Chapter 11. Security and Cryptography

Prior to version 4.0, the .NET Framework incorporated two complementary security models that addressed user and code security: role-based security (RBS) and code access security (CAS). With version 4.0, CAS has been deprecated. The previous edition of this book included a number of CAS recipes (11-1 through to 11-7), and we have included them in this new version because of the number of developers still using .NET 3.5 or earlier, where CAS still has a significant function. The C# compiler will show deprecation warnings if you use CAS in a project. You can prevent these errors by targeting your project at an earlier version of the .NET Framework on the Application tab. You can enable CAS in your .NET 4.0 projects with the NetFx40_LegacySecurityPolicy configuration element in the app.config file—for example:

<?xml version="1.0"?>
<configuration>
  <runtime>
    <NetFx40_LegacySecurityPolicy enabled="true"/>
  </runtime>
</configuration>

RBS remains current in .NET 4.0 and allows you to make runtime decisions based on the identity and roles of the user on whose behalf an application is running. On the Windows operating system, this equates to making decisions based on the Windows username and the Windows groups to which that user belongs. However, RBS provides a generic security mechanism that is independent of the underlying operating system, allowing you (with some development) to integrate with any user account system.

An important aspect of the security features provided by the .NET Framework is cryptography. Cryptography is one of the most complex aspects of software development that any developer will use. The theory of modern cryptographic techniques is extremely difficult to understand and requires a level of mathematical knowledge that relatively few people have or need. Fortunately, the Microsoft .NET Framework class library provides easy-to-use implementations of the most commonly used cryptographic techniques and support for the most popular and well-understood algorithms.

This chapter provides a wide variety of recipes that cover some of the more commonly used security capabilities provided by the .NET Framework. As you read the recipes in this chapter and think about how to apply the techniques to your own code, keep in mind that individual security features are rarely effective when implemented in isolation. In particular, cryptography does not equal security; the use of cryptography is merely one small element of creating a secure solution.

The recipes in this chapter describe how to do the following:

  • Develop strongly named assemblies that can still be called by partially trusted code (recipe 11-1)

  • Configure the .NET Framework security policy to turn off CAS completely or turn off only execution permission checks (recipes 11-2 and 11-3)

  • Request specific code access permissions for your assemblies, determine at runtime what permissions the current assembly has, and inspect third-party assemblies to determine what permissions they need in order to run correctly (recipes 11-4, 11-5, 11-6, and 11-7)

  • Control inheritance and member overrides using CAS (recipe 11-8)

  • Inspect the evidence presented by an assembly to the runtime when the assembly is loaded (recipe 11-9)

  • Integrate with Windows security to determine if a user is a member of a specific Windows group, restrict which users can execute your code, and impersonate other Windows users (recipes 11-10, 11-11, and 11-12)

  • Generate random numbers that are nondeterministic and are suitable for use in security-sensitive applications (recipe 11-13)

  • Use hash codes and keyed hash codes to store user passwords and determine if files have changed (recipes 11-14, 11-15, 11-16, and 11-17)

  • Use encryption to protect sensitive data both in memory and when it is stored to disk (recipes 11-18 and 11-19)

Allow Partially Trusted Code to Use Your Strongly Named Assembly

Problem

You need to write a shared assembly that is accessible to partially trusted code. (By default, the runtime does not allow partially trusted code to access the types and members contained in a strongly named assembly.)

Solution

Apply the assembly-level attribute System.Security.AllowPartiallyTrustedCallersAttribute to your shared assembly.

Note

CAS is deprecated in .NET 4.0.

How It Works

To minimize the security risks posed by malicious code, the runtime does not allow assemblies granted only partial trust to access strongly named assemblies. This restriction dramatically reduces the opportunity for malicious code to attack your system, but the reasoning behind such a heavy-handed approach requires some explanation.

Assemblies that contain important functionality that is shared between multiple applications are usually strongly named and are often installed in the Global Assembly Cache (GAC). This is particularly true of the assemblies that constitute the .NET Framework class library. Other strongly named assemblies from well-known and widely distributed products are in the GAC and accessible to managed applications. The high chance that certain assemblies will be present in the GAC, their easy accessibility, and their importance to many different applications make strongly named assemblies the most likely target for any type of subversive activity by malicious managed code.

Generally, the code most likely to be malicious is that which is loaded from remote locations over which you have little or no control (such as over the Internet). Under the default security policy of the .NET Framework, all code run from the local machine has full trust, whereas code loaded from remote locations has only partial trust. Stopping partially trusted code from accessing strongly named assemblies means that partially trusted code has no opportunity to use the features of the assembly for malicious purposes, and cannot probe and explore the assembly to find exploitable holes. Of course, this theory hinges on the assumption that you correctly administer your security policy. If you simply assign all code full trust, not only will any assembly be able to access your strongly named assembly, but the code will also be able to access all of the functionality of the .NET Framework and even Win32 or any COM object through P/Invoke and COM Interop. That would be a security disaster!

Note

If you design, implement, and test your shared assembly correctly using CAS to restrict access to important members, you do not need to impose a blanket restriction to prevent partially trusted code from using your assembly. However, for an assembly of any significance, it's impossible to prove there are no security holes that malicious code can exploit. Therefore, you should carefully consider the need to allow partially trusted code to access your strongly named assembly before applying AllowPartiallyTrustedCallersAttribute. However, you might have no choice. If you are exposing public classes that provide events, you must apply this attribute. If you do not, an assembly that is not strongly named will be allowed to register a handler for one of your events, but when it is called, a security exception will be thrown. Code in an assembly that is not strongly named is not allowed to call code in a strongly named assembly.

The runtime stops partially trusted code from accessing strongly named assemblies by placing an implicit LinkDemand for the FullTrust permission set on every public and protected member of every publicly accessible type defined in the assembly. This means that only assemblies granted the permissions equivalent to the FullTrust permission set are able to access the types and members from the strongly named assembly. Applying AllowPartiallyTrustedCallersAttribute to your strongly named assembly signals the runtime to not enforce the LinkDemand on the contained types and members.

Note

The runtime is responsible for enforcing the implicit LinkDemand security actions required to protect strongly named assemblies. The C# assembler does not generate declarative LinkDemand statements at compile time.

The Code

The following code fragment shows the application of the attribute AllowPartiallyTrustedCallersAttribute. Notice that you must prefix the attribute with assembly: to signal to the compiler that the target of the attribute is the assembly (also called a global attribute). In addition, you do not need to include the Attribute part of the attribute name, although you can if you want to add it. Because you target the assembly, the attribute must be positioned after any top-level using statements, but before any namespace or type declarations.

using System.Security;

[assembly:AllowPartiallyTrustedCallers]

namespace Apress.VisualCSharpRecipes.Chapter11
{
    public class Recipe11-01 {

        // Implementation code . . .
    }
}

Tip

It's common practice to contain all global attributes in a file separate from the rest of your application code. Microsoft Visual Studio uses this approach, creating a file named AssemblyInfo.cs to contain all global attributes.

Notes

If, after applying AllowPartiallyTrustedCallersAttribute to your assembly, you want to restrict partially trusted code from calling only specific members, you should implement a LinkDemand for the FullTrust permission set on the necessary members, as shown in the following code fragment:

[System.Security.Permissions.PermissionSetAttribute
    (System.Security.Permissions.SecurityAction.LinkDemand, Name="FullTrust")]

public void SomeMethod() {
    // Method code . . .
}

Disable Code Access Security

Problem

You need to turn off all CAS checks.

Solution

Use the Code Access Security Policy tool (Caspol.exe) and execute the command caspol -s off from the command line to temporarily disable code access security checks.

Note

This recipe only applies to .NET version 3.5 and earlier.

How It Works

Although CAS was implemented with performance in mind and has been used prudently throughout the .NET class library, some overhead is associated with each security demand and resulting stack walk that the runtime must execute to check every caller in the chain of execution.

You can temporarily disable CAS and remove the overhead and possible interference caused by code-level security checks. Turning off CAS has the effect of giving all code the ability to perform any action supported by the .NET Framework (equivalent to the FullTrust permission set). This includes the ability to load other code, call native libraries, and use pointers to access memory directly.

Caspol.exe is a utility provided with the .NET Framework that allows you to configure all aspects of your code access security policy from the command line. When you enter the command caspol -s off from the command line, you will see the following message indicating that CAS has been temporarily disabled:

Microsoft (r) .NET Framework CasPol 2.0.50727.42
Copyright (c) Microsoft Corporation. Al rights reserved.

CAS enforcement is being turned off temporarily. Press <enter> when you want to
restore the setting back on.

As the message states, CAS enforcement is off until you press Enter, or until the console in which Caspol.exe is running terminates.

Disable Execution Permission Checks

Problem

You need to load assemblies at runtime without the runtime checking them for execution permission.

Solution

In code, set the property CheckExecutionRights of the class System.Security.SecurityManager to false and persist the change by calling SecurityManager.SavePolicy. Alternatively, use the Code Access Security Policy tool (Caspol.exe), and execute the command caspol -e off from the command line.

Note

This recipe only applies to .NET version 3.5 and earlier.

How It Works

As the runtime loads each assembly, it ensures that the assembly's grant set (the permissions assigned to the assembly based on the security policy) includes the Execution element of SecurityPermission. The runtime implements a lazy policy resolution process, meaning that the grant set of an assembly is not calculated until the first time a security demand is made against the assembly. Not only does execution permission checking force the runtime to check that every assembly has the execution permission, but it also indirectly causes policy resolution for every assembly loaded, effectively negating the benefits of lazy policy resolution. These factors can introduce a noticeable delay as assemblies are loaded, especially when the runtime loads a number of assemblies together, as it does at application startup.

In many situations, simply allowing code to load and run is not a significant risk, as long as all other important operations and resources are correctly secured using CAS and operating system security. The SecurityManager class contains a set of static methods that provide access to critical security functionality and data. This includes the CheckExecutionRights property, which turns on and off execution permission checks.

To modify the value of CheckExecutionRights, your code must have the ControlPolicy element of SecurityPermission. The change will affect the current process immediately, allowing you to load assemblies at runtime without the runtime checking them for execution permission. However, the change will not affect other existing processes. You must call the SavePolicy method to persist the change to the Windows registry for it to affect new processes.

The Code

The following example contains two methods (ExecutionCheckOn and ExecutionCheckOff) that demonstrate the code required to turn execution permission checks on and off and persist the configuration change. You may need to run the example with administrator privileges.

using System;
using System.Security;

namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_03
    {
        // A method to turn on execution permission checking
        // and persist the change.
        public void ExecutionCheckOn()
        {
            // Turn on execution permission checks.
            SecurityManager.CheckExecutionRights = true;

            // Persist the configuration change.
            SecurityManager.SavePolicy();
        }

        // A method to turn off execution permission checking
        // and persist the change.
        public void ExecutionCheckOff()
        {
            // Turn off execution permission checks.
            SecurityManager.CheckExecutionRights = false;

            // Persist the configuration change.
            SecurityManager.SavePolicy();
        }
    }
}

Notes

The .NET runtime allows you to turn off the automatic checks for execution permissions from within code or by using Caspol.exe. When you enter the command caspol -e off or its counterpart caspol -e on from the command line, the Caspol.exe utility actually sets the CheckExecutionRights property of the SecurityManager class before calling SecurityManager.SavePolicy.

Ensure the Runtime Grants Specific Permissions to Your Assembly

Problem

You need to ensure that the runtime grants your assembly those code access permissions that are critical to the successful operation of your application.

Solution

In your assembly, use permission requests to specify the code access permissions that your assembly must have. You declare permission requests using assembly-level code access permission attributes.

Note

CAS is deprecated in .NET 4.0.

How It Works

The name permission request is a little misleading given that the runtime will never grant permissions to an assembly unless security policy dictates that the assembly should have those permissions. However, naming aside, permission requests serve an essential purpose, and although the way the runtime handles permission requests might initially seem strange, the nature of CAS does not allow for any obvious alternative.

Permission requests identify permissions that your code must have to function. For example, if you wrote a movie player that your customers could use to download and view movies from your web server, it would be disastrous if the user's security policy did not allow your player to open a network connection to your media server. Your player would load and run, but as soon as the user tried to connect to your server to play a movie, the application would crash with the exception System.Security.SecurityException. The solution is to include in your assembly a permission request for the code access permission required to open a network connection to your server (System.Net.WebPermission or System.Net.SocketPermission, depending on the type of connection you need to open).

The runtime honors permission requests using the premise that it's better that your code never load than to load and fail sometime later when it tries to perform an action that it does not have permission to perform. Therefore, if after security policy resolution the runtime determines that the grant set of your assembly does not satisfy the assembly's permission requests, the runtime will fail to load the assembly and will instead throw the exception System.Security.Policy.PolicyException. Since your own code failed to load, the runtime will handle this security exception during the assembly loading and transform it into a System.IO.FileLoadException exception that will terminate your program.

When you try to load an assembly from within code (either automatically or manually), and the loaded assembly contains permission requests that the security policy does not satisfy, the method you use to load the assembly will throw a PolicyException exception, which you must handle appropriately.

To declare a permission request, you must use the attribute counterpart of the code access permission that you need to request. All code access permissions have an attribute counterpart that you use to construct declarative security statements, including permission requests. For example, the attribute counterpart of SocketPermission is SocketPermissionAttribute, and the attribute counterpart of WebPermission is WebPermissionAttribute. All permissions and their attribute counterparts follow the same naming convention and are members of the same namespace.

When making a permission request, it's important to remember the following:

  • You must declare the permission request after any top-level using statements but before any namespace or type declarations.

  • The attribute must target the assembly, so you must prefix the attribute name with assembly.

  • You do not need to include the Attribute portion of an attribute's name, although you can.

  • You must specify SecurityAction.RequestMinimum as the first positional argument of the attribute. This value identifies the statement as a permission request.

  • You must configure the attribute to represent the code access permission you want to request using the attribute's properties. Refer to the .NET Framework SDK documentation for details of the properties implemented by each code access security attribute.

  • The permission request statements do not end with a semicolon (;).

  • To make more than one permission request, simply include multiple permission request statements.

The Code

The following example is a console application that includes two permission requests: one for SocketPermission and the other for SecurityPermission. If you try to execute the PermissionRequestExample application and your security policy does not grant the assembly the requested permissions, you will get a PolicyException, and the application will not execute. Using the default security policy, this will happen if you run the assembly from a network share, because assemblies loaded from the intranet zone are not granted SocketPermission.

using System;
using System.Net;
using System.Security.Permissions;

// Permission request for a SocketPermission that allows the code to open
// a TCP connection to the specified host and port.
[assembly:SocketPermission(SecurityAction.RequestMinimum,
    Access = "Connect", Host = "www.fabrikam.com",
    Port = "3538", Transport = "Tcp")]

// Permission request for the UnmanagedCode element of SecurityPermission,
// which controls the code's ability to execute unmanaged code.
[assembly:SecurityPermission(SecurityAction.RequestMinimum,
    UnmanagedCode = true)]

namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_04
    {
        public static void Main()
        {
            // Do something . . .
// Wait to continue.
            Console.WriteLine("Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Limit the Permissions Granted to Your Assembly

Problem

You need to restrict the code access permissions granted to your assembly, ensuring that people and other software can never use your code as a mechanism through which to perform undesirable or malicious actions.

Solution

Use declarative security statements to specify optional permission requests and permission refusal requests in your assembly. Optional permission requests define the maximum set of permissions that the runtime will grant to your assembly. Permission refusal requests specify particular permissions that the runtime should not grant to your assembly.

Note

CAS is deprecated in .NET 4.0.

How It Works

In the interest of security, it's ideal if your code has only those code access permissions required to perform its function. This minimizes the opportunities for people and other code to use your code to carry out malicious or undesirable actions. The problem is that the runtime resolves an assembly's permissions using security policy, which a user or an administrator configures. Security policy could be different in every location where your application is run, and you have no control over what permissions the security policy assigns to your code.

Although you cannot control security policy in all locations where your code runs, the .NET Framework provides two mechanisms through which you can reject permissions granted to your assembly:

  • Refuse request: This allows you to identify specific permissions that you do not want the runtime to grant to your assembly. After policy resolution, if the final grant set of an assembly contains any permission specified in a refuse request, the runtime removes that permission.

  • Optional permission request: This defines the maximum set of permissions that the runtime can grant to your assembly. If the final grant set of an assembly contains any permissions other than those specified in the optional permission request, the runtime removes those permissions. Unlike as with a minimum permission request (discussed in recipe 11-4), the runtime will not refuse to load your assembly if it cannot grant all of the permissions specified in the optional request.

You can think of a refuse request and an optional request as alternative ways to achieve the same result. The approach you use depends on how many permissions you want to reject. If you want to reject only a handful of permissions, a refuse request is easier to code. However, if you want to reject a large number of permissions, it's easier to code an optional request for the few permissions you want, which will automatically reject the rest.

You include optional and refuse requests in your code using declarative security statements with the same syntax as the minimum permission requests discussed in recipe 11-4. The only difference is the value of the System.Security.Permissions.SecurityAction that you pass to the permission attribute's constructor. Use SecurityAction.RequestOptional to declare an optional permission request and SecurityAction.RequestRefuse to declare a refuse request. As with minimal permission requests, you must declare optional and refuse requests as global attributes by beginning the permission attribute name with the prefix assembly. In addition, all requests must appear after any top-level using statements but before any namespace or type declarations.

The Code

The code shown here demonstrates an optional permission request for the Internet permission set. The Internet permission set is a named permission set defined by the default security policy. When the runtime loads the example, it will not grant the assembly any permission that is not included within the Internet permission set. (Consult the .NET Framework SDK documentation for details of the permissions contained in the Internet permission set.)

using System.Security.Permissions;

[assembly:PermissionSet(SecurityAction.RequestOptional, Name = "Internet")]

namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_05_OptionalRequest
    {
        // Class implementation . . .
    }
}

In contrast to the preceding example, the following example uses a refuse request to single out the permission System.Security.Permissions.FileIOPermission—representing write access to the C: drive—for refusal.

using System.Security.Permissions;

[assembly:FileIOPermission(SecurityAction.RequestRefuse, Write = @"C:")]

namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_05_RefuseRequest
    {
        // Class implementation . . .
    }
}

View the Permissions Required by an Assembly

Problem

You need to view the permissions that an assembly must be granted in order to run correctly.

Solution

Use the Permissions Calculator (Permcalc.exe) supplied with the .NET Framework SDK version 3.5 or earlier.

Note

CAS is deprecated in .NET 4.0.

How It Works

To configure security policy correctly, you need to know the code access permission requirements of the assemblies you intend to run. This is true of both executable assemblies and libraries that you access from your own applications. With libraries, it's also important to know which permissions the assembly refuses so that you do not try to use the library to perform a restricted action, which would result in a System.Security.SecurityException exception.

The Permissions Calculator (Permcalc.exe) supplied with the .NET Framework SDK version overcomes this limitation. Permcalc.exe walks through an assembly and provides an estimate of the permissions the assembly requires to run, regardless of whether they are declarative or imperative.

The Code

The following example shows a class that declares a minimum, optional, and refusal request, as well as a number of imperative security demands:

using System;
using System.Net;
using System.Security.Permissions;

// Minimum permission request for SocketPermission.
[assembly: SocketPermission(SecurityAction.RequestMinimum,
    Unrestricted = true)]

// Optional permission request for IsolatedStorageFilePermission.
[assembly: IsolatedStorageFilePermission(SecurityAction.RequestOptional,
    Unrestricted = true)]

// Refuse request for ReflectionPermission.
[assembly: ReflectionPermission(SecurityAction.RequestRefuse,
    Unrestricted = true)]

namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_06
    {
        public static void Main()
        {
            // Create and configure a FileIOPermission object that represents
            // write access to the C:Data folder.
            FileIOPermission fileIOPerm =
                new FileIOPermission(FileIOPermissionAccess.Write, @"C:Data");

            // Make the demand.
            fileIOPerm.Demand();

            // Do something . . .

            // Wait to continue.
            Console.WriteLine("Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Usage

Executing the command permview Recipe11-06.exe will generate the following output. Although this output is not particularly user-friendly, you can decipher it to determine the declarative permission requests made by an assembly. Each of the three types of permission requests—minimum, optional, and refused—is listed under a separate heading and is structured as the XML representation of a System.Security.PermissionSet object.

Microsoft (R) .NET Framework Permission Request Viewer.

Version 1.1.4322.573

Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.



minimal permission set:

<PermissionSet class=System.Security.PermissionSet" version="1">

  <IPermission class="System.Net.SocketPermission, System, Version=1.

0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="

1" Unrestricted="true"/>

</PermissionSet>



optional permission set:

<PermissionSet class="System.Security.PermissionSet" version="1">

  <IPermission class="System.Security.Permissions.IsolatedStorageFilePermission,

mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c5

61934e089" version="1" Unrestricted="true"/>

</PermissionSet>
refused permission set:

<PermissionSet class="System.Security.PermissionSet" version="1">

  <IPermission class="System.Security.Permissions.ReflectionPermission,

mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c5

61934e089" version="1" Unrestricted="true"/>

</PermissionSet>

Executing the command permcalc -sandbox Recipe11-06.exe will generate a file named sandbox.PermCalc.xml that contains XML representations of the permissions required by the assembly. Where the exact requirements of a permission cannot be determined (because it is based on runtime data), Permcalc.exe reports that unrestricted permissions of that type are required. You can instead default to the Internet zone permissions using the -Internet flag. Here are the contents of sandbox.PermCalc.xml when run against the sample code:

<?xml version="1.0"?>


<Sandbox>


  <PermissionSet version="1" class="System.Security.PermissionSet">


    <IPermission Write="C:Data" version="1"


        class="System.Security.Permissions.FileIOPermission, mscorlib,


        Version=2.0.0.0, Culture=neutral,


        PublicKeyToken=b77a5c561934e089" />


    <IPermission version="1" class="System.Security.Permissions.SecurityPermission,


        mscorlib, Version=2.0.0.0, Culture=neutral,


        PublicKeyToken=b77a5c561934e089" Flags="Execution" />


    <IPermission version="1" class="System.Security.Permissions.UIPermission,


        mscorlib, Version=2.0.0.0, Culture=neutral,


        PublicKeyToken=b77a5c561934e089" Unrestricted="true" />
<IPermission version="1" class="System.Net.SocketPermission, System,


         Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"


         Unrestricted="true" />


  </PermissionSet>


</Sandbox>

Determine at Runtime If Your Code Has a Specific Permission

Problem

You need to determine at runtime if your assembly has a specific permission.

Solution

Instantiate and configure the permission you want to test for, and then pass it as an argument to the static method IsGranted of the class System.Security.SecurityManager.

Note

CAS is deprecated in .NET 4.0.

How It Works

Using minimum permission requests, you can ensure that the runtime grants your assembly a specified set of permissions. As a result, when your code is running, you can safely assume that it has the requested minimum permissions. However, you might want to implement opportunistic functionality that your application offers only if the runtime grants your assembly appropriate permissions. This approach is partially formalized using optional permission requests, which allow you to define a set of permissions that your code could use if the security policy granted them, but are not essential for the successful operation of your code. (Recipe 11-5 provides more details on using optional permission requests.)

The problem with optional permission requests is that the runtime has no ability to communicate to your assembly which of the requested optional permissions it has granted. You can try to use a protected operation and fail gracefully if the call results in the exception System.Security.SecurityException. However, it's more efficient to determine in advance whether you have the necessary permissions. You can then build logic into your code to avoid invoking secured members that will cause stack walks and raise security exceptions.

Note

IsGranted checks the grant set only of the calling assembly. It does not do a full stack walk to evaluate the grant set of other assemblies on the call stack.

The Code

The following example demonstrates how to use the IsGranted method to determine if the assembly has write permission to the directory C:Data. You could make such a call each time you needed to test for the permission, but it's more efficient to use the returned Boolean value to set a configuration flag indicating whether to allow users to save files.

using System.Security;
using System.Security.Permissions;

namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_07
    {
        // Define a variable to indicate whether the assembly has write
        // access to the C:Data folder.
        private bool canWrite = false;

        public Recipe11_07()
        {
            // Create and configure a FileIOPermission object that represents
            // write access to the C:Data folder.
            FileIOPermission fileIOPerm =
                new FileIOPermission(FileIOPermissionAccess.Write, @"C:Data");
            // Test if the current assembly has the specified permission.
            canWrite = SecurityManager.IsGranted(fileIOPerm);
        }
    }
}

Restrict Who Can Extend Your Classes and Override Class Members

Problem

You need to control what code can extend your classes through inheritance and which class members a derived class can override.

Solution

Use declarative security statements to apply SecurityAction.InheritanceDemand to the declarations of the classes and members that you need to protect.

How It Works

Language modifiers such as sealed, public, private, and virtual give you a level of control over the ability of classes to inherit from your class and override its members. However, these modifiers are inflexible, providing no selectivity in restricting what code can extend a class or override its members. For example, you might want to allow only code written by your company or department to extend business-critical classes. By applying an InheritanceDemand attribute to your class or member declaration, you can specify runtime permissions that a class must have to extend your class or override particular members. Remember that the permissions of a class are the permissions of the assembly in which the class is declared.

Although you can demand any permission or permission set in your InheritanceDemand, it's more common to demand identity permissions. Identity permissions represent evidence presented to the runtime by an assembly. If an assembly presents certain types of evidence at load time, the runtime will automatically assign the assembly the appropriate identity permission. Identity permissions allow you to use regular imperative and declarative security statements to base security decisions directly on code identity, without the need to evaluate evidence objects directly. Table 11-1 lists the type of identity permission generated for each type of evidence. (Evidence types are members of the System.Security.Policy namespace, and identity permission types are members of the System.Security.Permissions namespace.)

Table 11.1. Evidence Classes That Generate Identity Permissions

Evidence Class

Identity Permission

ApplicationDirectory

None

Hash

None

Publisher

PublisherIdentityPermission

Site

SiteIdentityPermission

StrongName

StrongNameIdentityPermission

Url

UrlIdentityPermission

Zone

ZoneIdentityPermission

Note

The runtime assigns identity permissions to an assembly based on the evidence presented by the assembly. You cannot assign additional identity permissions to an assembly through the configuration of security policy.

You must use declarative security syntax to implement an InheritanceDemand, and so you must use the attribute counterpart of the permission class that you want to demand. All permission classes, including InheritanceDemand, have an attribute counterpart that you use to construct declarative security statements. For example, the attribute counterpart of PublisherIdentityPermission is PublisherIdentityPermissionAttribute, and the attribute counterpart of StrongNameIdentityPermission is StrongNameIdentityPermissionAttribute. All permissions and their attribute counterparts follow the same naming convention and are members of the same namespace.

To control which code can extend your class, apply the InheritanceDemand to the class declaration using one of the permissions listed in Table 11-1. To control which code can override specific members of a class, apply the InheritanceDemand to the member declaration.

The Code

The following example demonstrates the use of an InheritanceDemand attribute on both a class and a method. Applying a PublisherIdentityPermissionAttribute to the Recipe11_08 class means that only classes in assemblies signed by the publisher certificate contained in the pubcert.cer file (or assemblies granted FullTrust) can extend the class. The contents of the pubcert.cer file are read at compile time, and the necessary certificate information is built into the assembly metadata. To demonstrate that other permissions can also be used with an InheritanceDemand, the PermissionSetAttribute is used to allow only classes granted the FullTrust permission set to override the method SomeProtectedMethod.

using System.Security.Permissions;

namespace Apress.VisualCSharpRecipes.Chapter11
{
    [PublisherIdentityPermission(SecurityAction.InheritanceDemand,
        CertFile = "pubcert.cer")]
    public class Recipe11_08
    {
        [PermissionSet(SecurityAction.InheritanceDemand, Name="FullTrust")]
        public void SomeProtectedMethod ()
        {
            // Method implementation . . .
        }
    }
}

Inspect an Assembly's Evidence

Problem

You need to inspect the evidence that the runtime assigned to an assembly.

Solution

Obtain a System.Reflection.Assembly object that represents the assembly in which you are interested. Get the System.Security.Policy.Evidence collection from the Evidence property of the Assembly object, and access the contained evidence objects using the GetEnumerator, GetHostEnumerator, or GetAssemblyEnumerator method of the Evidence class.

How It Works

The Evidence class represents a collection of evidence objects. The read-only Evidence property of the Assembly class returns an Evidence collection object that contains all of the evidence objects that the runtime assigned to the assembly as the assembly was loaded.

The Evidence class actually contains two collections, representing different types of evidence:

  • Host evidence includes those evidence objects assigned to the assembly by the runtime or the trusted code that loaded the assembly.

  • Assembly evidence represents custom evidence objects embedded into the assembly at build time.

The Evidence class implements three methods for enumerating the evidence objects it contains: GetEnumerator, GetHostEnumerator, and GetAssemblyEnumerator. The GetHostEnumerator and GetAssemblyEnumerator methods return a System.Collections.IEnumerator instance that enumerates only those evidence objects from the appropriate collection. The GetEnumerator method returns an IEnumerator instance that enumerates all of the evidence objects contained in the Evidence collection.

Note

Evidence classes do not extend a standard base class or implement a standard interface. Therefore, when working with evidence programmatically, you need to test the type of each object and know what particular types you are seeking. (See recipe 3-11 for details on how to test the type of an object at runtime.)

The Code

The following example demonstrates how to display the host and assembly evidence of an assembly to the console. The example relies on the fact that all standard evidence classes override the Object.ToString method to display a useful representation of the evidence object's state. Although interesting, this example does not always show the evidence that an assembly would have when loaded from within your program. The runtime host (such as the Microsoft ASP.NET or Internet Explorer runtime host) is free to assign additional host evidence as it loads an assembly.

using System;
using System.Reflection;
using System.Collections;
using System.Security.Policy;

namespace Apress.VisualCSharpRecipes.Chapter11
{
    public class Recipe11_09
    {
        public static void Main(string[] args)
        {
            // Load the specified assembly.
            Assembly a = Assembly.LoadFrom(args[0]);

            // Get the Evidence collection from the
            // loaded assembly.
            Evidence e = a.Evidence;

            // Display the host evidence.
            IEnumerator x = e.GetHostEnumerator();
            Console.WriteLine("HOST EVIDENCE COLLECTION:");
            while(x.MoveNext())
            {
                Console.WriteLine(x.Current.ToString());
                Console.WriteLine("Press Enter to see next evidence.");
                Console.ReadLine();
            }

            // Display the assembly evidence.
            x = e.GetAssemblyEnumerator();
            Console.WriteLine("ASSEMBLY EVIDENCE COLLECTION:");
            while(x.MoveNext())
            {
                Console.WriteLine(x.Current.ToString());
                Console.WriteLine("Press Enter to see next evidence.");
                Console.ReadLine();
            }

            // Wait to continue.
            Console.WriteLine("Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Determine If the Current User Is a Member of a Specific Windows Group

Problem

You need to determine if the current user of your application is a member of a specific Windows user group.

Solution

Obtain a System.Security.Principal.WindowsIdentity object representing the current Windows user by calling the static method WindowsIdentity.GetCurrent. Create a System.Security.Principal.WindowsPrincipal class using the WindowsIdentity class, and then call the method IsInRole of the WindowsPrincipal object.

How It Works

The RBS mechanism of the .NET Framework abstracts the user-based security features of the underlying operating system through the following two key interfaces:

  • The System.Security.Principal.IIdentity interface, which represents the entity on whose behalf code is running; for example, a user or service account.

  • The System.Security.Principal.IPrincipal interface, which represents the entity's IIdentity and the set of roles to which the entity belongs. A role is simply a categorization used to group entities with similar security capabilities, such as a Windows user group.

To integrate RBS with Windows user security, the .NET Framework provides the following two Windows-specific classes that implement the IIdentity and IPrincipal interfaces:

  • System.Security.Principal.WindowsIdentity, which implements the IIdentity interface and represents a Windows user.

  • System.Security.Principal.WindowsPrincipal, which implements IPrincipal and represents the set of Windows groups to which the user belongs.

Because .NET RBS is a generic solution designed to be platform-independent, you have no access to the features and capabilities of the Windows user account through the IIdentity and IPrincipal interfaces, and you must frequently use the WindowsIdentity and WindowsPrincipal objects directly.

To determine if the current user is a member of a specific Windows group, you must first call the static method WindowsIdentity.GetCurrent. The GetCurrent method returns a WindowsIdentity object that represents the Windows user on whose behalf the current thread is running. An overload of the GetCurrent method takes a bool argument and allows you to control what is returned by GetCurrent if the current thread is impersonating a user different from the one associated with the process. If the argument is true, then GetCurrent returns a WindowsIdentity representing the impersonated user, and it returns null if the thread is not impersonating a user. If the argument is false, then GetCurrent returns the WindowsIdentity of the thread if it is not impersonating a user, and it returns the WindowsIdentity of the process if the thread is currently impersonating a user.

Note

The WindowsIdentity class provides overloaded constructors that, when running on Microsoft Windows Server 2003 or later platforms, allow you to obtain a WindowsIdentity object representing a named user. You can use this WindowsIdentity object and the process described in this recipe to determine whether that user is a member of a specific Windows group. If you try to use one of these constructors when running on an earlier version of Windows, the WindowsIdentity constructor will throw an exception. On Windows platforms preceding Windows Server 2003, you must use native code to obtain a Windows access token representing the desired user. You can then use this access token to instantiate a WindowsIdentity object. Recipe 11-12 explains how to obtain Windows access tokens for specific users.

Once you have a WindowsIdentity, instantiate a new WindowsPrincipal object, passing the WindowsIdentity object as an argument to the constructor. Finally, call the IsInRole method of the WindowsPrincipal object to test if the user is in a specific group (role). IsInRole returns true if the user is a member of the specified group; otherwise, it returns false. The IsInRole method provides four overloads:

  • The first overload takes a string containing the name of the group for which you want to test. The group name must be of the form [DomainName][GroupName] for domain-based groups and [MachineName][GroupName] for locally defined groups. If you want to test for membership of a standard Windows group, use the form BUILTIN[GroupName] or the other overload that takes a value from the System.Security.Principal.WindowsBuiltInRole enumeration. IsInRole performs a case-insensitive test for the specified group name.

  • The second IsInRole overload accepts an int, which specifies a Windows role identifier (RID). RIDs provide a mechanism that is independent of language and localization to identify groups.

  • The third IsInRole overload accepts a member of the System.Security.Principal.WindowsBuiltInRole enumeration. The WindowsBuiltInRole enumeration defines a set of members that represent each of the built-in Windows groups.

  • The fourth IsInRole overload accepts a System.Security.Principal.SecurityIdentifier object that represents the security identifier (SID) of the group for which you want to test.

Table 11-2 lists the name, RID, and WindowsBuiltInRole value for each of the standard Windows groups.

Table 11.2. Windows Built-In Account Names and Identifiers

Account Name

RID (Hex)

WindowsBuiltInRole Value

BUILTINAccount Operators

0x224

AccountOperator

BUILTINAdministrators

0x220

Administrator

BUILTINBackup Operators

0x227

BackupOperator

BUILTINGuests

0x222

Guest

BUILTINPower Users

0x223

PowerUser

BUILTINPrint Operators

0x226

PrintOperator

BUILTINReplicators

0x228

Replicator

BUILTINServer Operators

0x225

SystemOperator

BUILTINUsers

0x221

User

Note

Membership of the BUILTINAdministrators group under Windows 7 will depend on the whether your process is running with elevated privileges. If the current user is an administrator but your process is running without elevated privileges, checking membership of BUILTINAdministrators will return false. See Chapter 14 for recipes relating to elevated privileges.

The Code

The following example demonstrates how to test whether the current user is a member of a set of named "Windows groups." You specify the groups that you want to test for as command-line arguments. Remember to prefix the group name with the machine or domain name, or BUILTIN for standard Windows groups.

using System;
using System.Security.Principal;

namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_10
    {
        public static void Main (string[] args)
        {
if (args.Length == 0)
            {
                Console.WriteLine(
                    "Please provide groups to check as command line arguments");
            }

            // Obtain a WindowsIdentity object representing the currently
            // logged-on Windows user.
            WindowsIdentity identity = WindowsIdentity.GetCurrent();

            // Create a WindowsPrincipal object that represents the security
            // capabilities of the specified WindowsIdentity; in this case,
            // the Windows groups to which the current user belongs.
            WindowsPrincipal principal = new WindowsPrincipal(identity);

            // Iterate through the group names specified as command-line
            // arguments and test to see if the current user is a member of
            // each one.
            foreach (string role in args)
            {
                Console.WriteLine("Is {0} a member of {1}? = {2}",
                    identity.Name, role, principal.IsInRole(role));
            }

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Usage

If you run this example as a user named Darryl on a computer named MACHINE using this command:

Recipe11-10 BUILTINAdministrators BUILTINUsers MACHINEAccountants

you will see console output similar to the following:

Is MACHINEDarryl a member of BUILTINAdministrators? = False

Is MACHINEDarryl a member of BUILTINUsers? = True

Is MACHINEDarryl a member of MACHINEAccountants? = True

Restrict Which Users Can Execute Your Code

Problem

You need to restrict which users can execute elements of your code based on the user's name or the roles of which the user is a member.

Solution

Use the permission class System.Security.Permissions.PrincipalPermission and its attribute counterpart System.Security.Permissions.PrincipalPermissionAttribute to protect your program elements with RBS demands.

How It Works

The .NET Framework supports both imperative and declarative RBS demands. The class PrincipalPermission provides support for imperative security statements, and its attribute counterpart PrincipalPermissionAttribute provides support for declarative security statements. RBS demands use the same syntax as CAS demands, but RBS demands specify the name the current user must have, or more commonly, the roles of which the user must be a member. An RBS demand instructs the runtime to look at the name and roles of the current user, and if that user does not meet the requirements of the demand, the runtime throws a System.Security.SecurityException exception.

To make an imperative security demand, you must first create a PrincipalPermission object specifying the username and role name you want to demand, and then you must call its Demand method. You can specify only a single username and role name per demand. If either the username or the role name is null, any value will satisfy the demand. Unlike with code access permissions, an RBS demand does not result in a stack walk; the runtime evaluates only the username and roles of the current user.

To make a declarative security demand, you must annotate the class or member you want to protect with a correctly configured PrincipalPermissionAttribute attribute. Class-level demands apply to all members of the class, unless a member-specific demand overrides the class demand.

Generally, you are free to choose whether to implement imperative or declarative demands. However, imperative security demands allow you to integrate RBS demands with code logic to achieve more sophisticated demand behavior. In addition, if you do not know the role or usernames to demand at compile time, you must use imperative demands. Declarative demands have the advantage that they are separate from code logic and easier to identify. In addition, you can view declarative demands using the Permview.exe tool (discussed in recipe 11-6). Whether you implement imperative or declarative demands, you must ensure that the runtime has access to the name and roles for the current user to evaluate the demand correctly.

The System.Threading.Thread class represents an operating system thread running managed code. The static property CurrentPrincipal of the Thread class contains an IPrincipal instance representing the user on whose behalf the managed thread is running. At the operating system level, each thread also has an associated Windows access token, which represents the Windows account on whose behalf the thread is running. The IPrincipal instance and the Windows access token are two separate entities. Windows uses its access token to enforce operating system security, whereas the .NET runtime uses its IPrincipal instance to evaluate application-level RBS demands. Although they may, and often do, represent the same user, this is by no means always the case.

The benefit of this approach is that you can implement a user and an RBS model within your application using a proprietary user accounts database, without the need for all users to have Windows user accounts. This is a particularly useful approach in large-scale, publicly accessible Internet applications.

By default, the Thread.CurrentPrincipal property is undefined. Because obtaining user-related information can be time-consuming, and only a minority of applications use this information, the .NET designers opted for lazy initialization of the CurrentPrincipal property. The first time code gets the Thread.CurrentPrincipal property, the runtime assigns an IPrincipal instance to the property using the following logic:

  • If the application domain in which the current thread is executing has a default principal, the runtime assigns this principal to the Thread.CurrentPrincipal property. By default, application domains do not have default principals. You can set the default principal of an application domain by calling the SetThreadPrincipal method on a System.AppDomain object that represents the application domain you want to configure. Code must have the ControlPrincipal element of SecurityPermission to call SetThreadPrincipal. You can set the default principal only once for each application domain; a second call to SetThreadPrincipal results in the exception System.Security.Policy.PolicyException.

  • If the application domain does not have a default principal, the application domain's principal policy determines which IPrincipal implementation to create and assign to Thread.CurrentPrincipal. To configure principal policy for an application domain, obtain an AppDomain object that represents the application domain and call the object's SetPrincipalPolicy method. The SetPrincipalPolicy method accepts a member of the enumeration System.Security.Principal.PrincipalPolicy, which specifies the type of IPrincipal object to assign to Thread.CurrentPrincipal. Code must have the ControlPrincipal element of SecurityPermission to call SetPrincipalPolicy. Table 11-3 lists the available PrincipalPolicy values; the default value is UnauthenticatedPrincipal.

  • If your code has the ControlPrincipal element of SecurityPermission, you can instantiate your own IPrincipal object and assign it to the Thread.CurrentPrincipal property directly. This will prevent the runtime from assigning default IPrincipal objects or creating new ones based on principal policy.

Table 11.3. Members of the PrincipalPolicy Enumeration

Member Name

Description

NoPrincipal

No IPrincipal object is created. Thread.CurrentPrincipal returns a null reference.

UnauthenticatedPrincipal

An empty System.Security.Principal.GenericPrincipal object is created and assigned to Thread.CurrentPrincipal.

WindowsPrincipal

A WindowsPrincipal object representing the currently logged-on Windows user is created and assigned to Thread.CurrentPrincipal.

Whatever method you use to establish the IPrincipal for the current thread, you must do so before you use RBS demands, or the correct user (IPrincipal) information will not be available for the runtime to process the demand. Normally, when running on the Windows platform, you would set the principal policy of an application domain to PrincipalPolicy.WindowsPrincipal (as shown here) to obtain Windows user information.

// Obtain a reference to the current application domain.
AppDomain appDomain = System.AppDomain.CurrentDomain;

// Configure the current application domain to use Windows-based principals.
appDomain.SetPrincipalPolicy(
    System.Security.Principal.PrincipalPolicy.WindowsPrincipal);

The Code

The following example demonstrates the use of imperative and declarative RBS demands. The example shows three methods protected using imperative RBS demands (Method1, Method2, and Method3), and then three other methods protected using the equivalent declarative RBS demands (Method4, Method5, and Method6).

using System;
using System.Security.Permissions;

namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_11
    {
        public static void Method1()
        {
            // An imperative role-based security demand for the current principal
            // to represent an identity with the name Anya. The roles of the
            // principal are irrelevant.
            PrincipalPermission perm =
                new PrincipalPermission(@"MACHINEAnya", null);
// Make the demand.
            perm.Demand();
        }

        public static void Method2()
        {
            // An imperative role-based security demand for the current principal
            // to be a member of the roles Managers OR Developers. If the
            // principal is a member of either role, access is granted. Using the
            // PrincipalPermission, you can express only an OR-type relationship.
            // This is because the PrincipalPolicy.Intersect method always
            // returns an empty permission unless the two inputs are the same.
            // However, you can use code logic to implement more complex
            // conditions. In this case, the name of the identity is irrelevant.
            PrincipalPermission perm1 =
                new PrincipalPermission(null, @"MACHINEManagers");

            PrincipalPermission perm2 =
                new PrincipalPermission(null, @"MACHINEDevelopers");

            // Make the demand.
            perm1.Union(perm2).Demand();
        }

        public static void Method3()
        {
            // An imperative role-based security demand for the current principal
            // to represent an identity with the name Anya AND be a member of the
            // Managers role.
            PrincipalPermission perm =
                new PrincipalPermission(@"MACHINEAnya", @"MACHINEManagers");
            // Make the demand.
            perm.Demand();
        }

        // A declarative role-based security demand for the current principal
        // to represent an identity with the name Anya. The roles of the
        // principal are irrelevant.
        [PrincipalPermission(SecurityAction.Demand, Name = @"MACHINEAnya")]
        public static void Method4()
        {
            // Method implementation . . .
        }

        // A declarative role-based security demand for the current principal
        // to be a member of the roles Managers OR Developers. If the
        // principal is a member of either role, access is granted. You
        // can express only an OR type relationship, not an AND relationship.
        // The name of the identity is irrelevant.
        [PrincipalPermission(SecurityAction.Demand, Role = @"MACHINEManagers")]
[PrincipalPermission(SecurityAction.Demand, Role = @"MACHINEDevelopers")]
        public static void Method5()
        {
            // Method implementation . . .
        }

        // A declarative role-based security demand for the current principal
        // to represent an identity with the name Anya AND be a member of the
        // Managers role.
        [PrincipalPermission(SecurityAction.Demand, Name = @"MACHINEAnya",
            Role = @"MACHINEManagers")]
        public static void Method6()
        {
            // Method implementation . . .
        }
    }
}

Impersonate a Windows User

Problem

You need your code to run in the context of a Windows user other than the currently active user account.

Solution

Obtain a System.Security.Principal.WindowsIdentity object representing the Windows user you need to impersonate, and then call the Impersonate method of the WindowsIdentity object.

How It Works

Every Windows thread has an associated access token, which represents the Windows account on whose behalf the thread is running. The Windows operating system uses the access token to determine whether a thread has the appropriate permissions to perform protected operations on behalf of the account, such as read and write files, reboot the system, and change the system time.

By default, a managed application runs in the context of the Windows account that executed the application. This is normally desirable behavior, but sometimes you will want to run an application in the context of a different Windows account. This is particularly true in the case of server-side applications that process transactions on behalf of the users remotely connected to the server.

It's common for a server application to run in the context of a Windows account created specifically for the application—a service account. This service account will have minimal permissions to access system resources. Enabling the application to operate as though it were the connected user permits the application to access the operations and resources appropriate to that user's security clearance. When an application assumes the identity of another user, it's known as impersonation. Correctly implemented, impersonation simplifies security administration and application design while maintaining user accountability.

Note

As discussed in recipe 11-11, a thread's Windows access token and its .NET principal are separate entities and can represent different users. The impersonation technique described in this recipe changes only the Windows access token of the current thread; it does not change the thread's principal. To change the thread's principal, code must have the ControlPrincipal element of SecurityPermission and assign a new System.Security.Principal.IPrincipal object to the CurrentPrincipal property of the current System.Threading.Thread.

The System.Security.Principal.WindowsIdentity class provides the functionality through which you invoke impersonation. However, the exact process depends on which version of Windows your application is running. If it's running on Windows Server 2003 or later, the WindowsIdentity class supports constructor overloads that create WindowsIdentity objects based on the account name of the user you want to impersonate. On all previous versions of Windows, you must first obtain a System.IntPtr containing a reference to a Windows access token that represents the user to impersonate. To obtain the access token reference, you must use a native method such as the LogonUser function from the Win32 API.

Once you have a WindowsIdentity object representing the user you want to impersonate, call its Impersonate method. From that point on, all actions your code performs occur in the context of the impersonated Windows account. The Impersonate method returns a System.Security.Principal.WindowsSecurityContext object, which represents the active account prior to impersonation. To revert to the original account, call the Undo method of this WindowsSecurityContext object.

The Code

The following example demonstrates impersonation of a Windows user. The example uses the LogonUser function of the Win32 API to obtain a Windows access token for the specified user, impersonates the user, and then reverts to the original user context.

using System;
using System.IO;
using System.Security.Principal;
using System.Security.Permissions;
using System.Runtime.InteropServices;

// Ensure the assembly has permission to execute unmanaged code
// and control the thread principal.
[assembly:SecurityPermission(SecurityAction.RequestMinimum,
    UnmanagedCode=true, ControlPrincipal=true)]

namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_12
    {
// Define some constants for use with the LogonUser function.
        const int LOGON32_PROVIDER_DEFAULT = 0;
        const int LOGON32_LOGON_INTERACTIVE = 2;

        // Import the Win32 LogonUser function from advapi32.dll. Specify
        // "SetLastError = true" to correctly support access to Win32 error
        // codes.
        [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
        static extern bool LogonUser(string userName, string domain,
            string password, int logonType, int logonProvider,
            ref IntPtr accessToken);

        public static void Main(string[] args)
        {
            // Create a new IntPtr to hold the access token returned by the
            // LogonUser function.
            IntPtr accessToken = IntPtr.Zero;

            // Call LogonUser to obtain an access token for the specified user.
            // The accessToken variable is passed to LogonUser by reference and
            // will contain a reference to the Windows access token if
            // LogonUser is successful.
            bool success = LogonUser(
                args[0],                    // Username to log on.
                ".",                        // Use the local account database.
                args[1],                    // User's password.
                LOGON32_LOGON_INTERACTIVE,  // Create an interactive login.
                LOGON32_PROVIDER_DEFAULT,   // Use the default logon provider.
                ref accessToken             // Receives access token handle.
            );
            // If the LogonUser return code is zero, an error has occurred.
            // Display the error and exit.
            if (!success)
            {
                Console.WriteLine("LogonUser returned error {0}",
                    Marshal.GetLastWin32Error());
            }
            else
            {
                // Create a new WindowsIdentity from the Windows access token.
                WindowsIdentity identity = new WindowsIdentity(accessToken);

                // Display the active identity.
                Console.WriteLine("Identity before impersonation = {0}",
                    WindowsIdentity.GetCurrent().Name);

                // Impersonate the specified user, saving a reference to the
                // returned WindowsImpersonationContext, which contains the
                // information necessary to revert to the original user
                // context.
WindowsImpersonationContext impContext =
                    identity.Impersonate();

                // Display the active identity.
                Console.WriteLine("Identity during impersonation = {0}",
                    WindowsIdentity.GetCurrent().Name);

                // *****************************************
                // Perform actions as the impersonated user.
                // *****************************************

                // Revert to the original Windows user using the
                // WindowsImpersonationContext object.
                impContext.Undo();

                // Display the active identity.
                Console.WriteLine("Identity after impersonation  = {0}",
                    WindowsIdentity.GetCurrent().Name);

                // Wait to continue.
                Console.WriteLine("
Main method complete. Press Enter.");
                Console.ReadLine();
            }
        }
    }
}

Usage

The example expects two command-line arguments: the account name of the user on the local machine to impersonate and the account's password. For example, the command Recipe11-12 Bob password impersonates the user Bob, as long as that user exists in the local accounts database and his password is "password."

Create a Cryptographically Random Number

Problem

You need to create a random number that is suitable for use in cryptographic and security applications.

Solution

Use a cryptographic random number generator such as the System.Security.Cryptography.RNGCryptoServiceProvider class.

How It Works

The System.Random class is a pseudorandom number generator that uses a mathematical algorithm to simulate the generation of random numbers. In fact, the algorithm it uses is deterministic, meaning that you can always calculate what the next number will be based on the previously generated number. This means that numbers generated by the Random class are unsuitable for use in situations in which security is a priority, such as generating encryption keys and passwords.

When you need a nondeterministic random number for use in cryptographic or security-related applications, you must use a random number generator derived from the class System.Security.Cryptography.RandomNumberGenerator. The RandomNumberGenerator class is an abstract class from which all concrete .NET random number generator classes should inherit. Currently, the RNGCryptoServiceProvider class is the only concrete implementation provided. The RNGCryptoServiceProvider class provides a managed wrapper around the CryptGenRandom function of the Win32 CryptoAPI, and you can use it to fill byte arrays with cryptographically random byte values.

Note

The numbers produced by the RNGCryptoServiceProvider class are not truly random. However, they are sufficiently random to meet the requirements of cryptography and security applications in most commercial and government environments.

As is the case with many of the .NET cryptography classes, the RandomNumberGenerator base class is a factory for the concrete implementation classes that derive from it. Calling RandomNumberGenerator.Create("System.Security.Cryptography.RNGCryptoServiceProvider") will return an instance of RNGCryptoServiceProvider that you can use to generate random numbers. In addition, because RNGCryptoServiceProvider is the only concrete implementation provided, it's the default class created if you call the Create method without arguments, as in RandomNumberGenerator.Create().

Once you have a RandomNumberGenerator instance, the method GetBytes fills a byte array with random byte values. As an alternative, you can use the GetNonZeroBytes method if you need random data that contains no zero values.

The Code

The following example instantiates an RNGCryptoServiceProvider object and uses it to generate random values.

using System;
using System.Security.Cryptography;

namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_13
    {
        public static void Main() {
            // Create a byte array to hold the random data.
            byte[] number = new byte[32];
// Instantiate the default random number generator.
            RandomNumberGenerator rng = RandomNumberGenerator.Create();

            // Generate 32 bytes of random data.
            rng.GetBytes(number);

            // Display the random number.
            Console.WriteLine(BitConverter.ToString(number));

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Note

The computational effort required to generate a random number with RNGCryptoServiceProvider is significantly greater than that required by Random. For everyday purposes, the use of RNGCryptoServiceProvider is overkill. You should consider the quantity of random numbers you need to generate and the purpose of the numbers before deciding to use RNGCryptoServiceProvider. Excessive and unnecessary use of the RNGCryptoServiceProvider class could have a noticeable effect on application performance if many random numbers are generated.

Calculate the Hash Code of a Password

Problem

You need to store a user's password securely so that you can use it to authenticate the user in the future.

Solution

Create and store a cryptographic hash code of the password using a hashing algorithm class derived from the System.Security.Cryptography.HashAlgorithm class. On future authentication attempts, generate the hash of the password entered by the user and compare it to the stored hash code.

Warning

You should never store a user's plain text password, because it is a major security risk and one that most users would not appreciate, given that many of them will use the same password to access multiple systems.

How It Works

Hashing algorithms are one-way cryptographic functions that take plain text of variable length and generate a fixed-size numeric value. They are one-way because it's nearly impossible to derive the original plain text from the hash code. Hashing algorithms are deterministic; applying the same hashing algorithm to a specific piece of plain text always generates the same hash code. This makes hash codes useful for determining if two blocks of plain text (passwords in this case) are the same. The design of hashing algorithms ensures that the chance of two different pieces of plain text generating the same hash code is extremely small (although not impossible). In addition, there is no correlation between the similarity of two pieces of plain text and their hash codes; minor differences in the plain text cause significant differences in the resulting hash codes.

When using passwords to authenticate a user, you are not concerned with the content of the password that the user enters. You need to know only that the entered password matches the password that you have recorded for that user in your accounts database.

The nature of hashing algorithms makes them ideal for storing passwords securely. When the user provides a new password, you must create the hash code of the password and store it, and then discard the plain text password. Each time the user tries to authenticate with your application, calculate the hash code of the password that user provides and compare it with the hash code you have stored.

Note

People regularly ask how to obtain a password from a hash code. The simple answer is that you cannot. The whole purpose of a hash code is to act as a token that you can freely store without creating security holes. If a user forgets a password, you cannot derive it from the stored hash code. Rather, you must either reset the account to some default value or generate a new password for the user.

Generating hash codes is simple in the .NET Framework. The abstract class HashAlgorithm provides a base from which all concrete hashing algorithm implementations derive. The .NET Framework class library includes the seven hashing algorithm implementations listed in Table 11-4; each implementation class is a member of the System.Security.Cryptography namespace. The classes with names ending in CryptoServiceProvider wrap functionality provided by the native Win32 CryptoAPI, whereas those with names ending in Managed are fully implemented in managed code.

Table 11.4. Hashing Algorithm Implementations

Algorithm Name

Class Name

Hash Code Size (in Bits)

MD5

MD5CryptoServiceProvider

128

RIPEMD160 or RIPEMD-160

RIPEMD160Managed

160

SHA or SHA1

SHA1CryptoServiceProvider

160

SHA1Managed

SHA1Managed

160

SHA256 or SHA-256

SHA256Managed

256

SHA384 or SHA-384

SHA384Managed

384

SHA512 or SHA-512

SHA512Managed

512

Although you can create instances of the hashing algorithm classes directly, the HashAlgorithm base class is a factory for the concrete implementation classes that derive from it. Calling the static method HashAlgorithm.Create will return an object of the specified type. Using the factory approach allows you to write generic code that can work with any hashing algorithm implementation. Note that unlike in recipe 11-13, you do not pass the class name as parameter to the factory; instead, you pass the algorithm name.

Once you have a HashAlgorithm object, its ComputeHash method accepts a byte array argument containing plain text and returns a new byte array containing the generated hash code. Table 11-4 shows the size of hash code (in bits) generated by each hashing algorithm class.

Note

The SHA1Managed algorithm cannot be implemented using the factory approach. It must be instantiated directly.

The Code

The example shown here demonstrates the creation of a hash code from a string, such as a password. The application expects two command-line arguments: the name of the hashing algorithm to use and the string from which to generate the hash. Because the HashAlgorithm.ComputeHash method requires a byte array, you must first byte-encode the input string using the class System.Text.Encoding, which provides mechanisms for converting strings to and from various character-encoding formats.

using System;
using System.Text;
using System.Security.Cryptography;
namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_14
    {
        public static void Main(string[] args)
        {
            // Create a HashAlgorithm of the type specified by the first
            // command-line argument.
            HashAlgorithm hashAlg = null;
            if (args[0].CompareTo("SHA1Managed") == 0)
            {
                hashAlg = new SHA1Managed();
            }
            else
            {
                hashAlg = HashAlgorithm.Create(args[0]);
            }

            using (hashAlg)
            {
                // Convert the password string, provided as the second
                // command-line argument, to an array of bytes.
                byte[] pwordData = Encoding.Default.GetBytes(args[1]);

                // Generate the hash code of the password.
                byte[] hash = hashAlg.ComputeHash(pwordData);

                // Display the hash code of the password to the console.
                Console.WriteLine(BitConverter.ToString(hash));

                // Wait to continue.
                Console.WriteLine("
Main method complete. Press Enter.");
                Console.ReadLine();
            }
        }
    }
}

Usage

Running the following command:

Recipe11-14 SHA1 ThisIsMyPassword

will display the following hash code to the console:

30-B8-BD-58-29-88-89-00-D1-5D-2B-BE-62-70-D9-BC-65-B0-70-2F

In contrast, executing this command:

Recipe11-14 RIPEMD-160 ThisIsMyPassword

will display the following hash code:

0C-39-3B-2E-8A-4E-D3-DD-FB-E3-C8-05-E4-62-6F-6B-76-7C-7A-49

Calculate the Hash Code of a File

Problem

You need to determine whether the contents of a file have changed over time.

Solution

Create a cryptographic hash code of the file's contents using the ComputeHash method of the System.Security.Cryptography.HashAlgorithm class. Store the hash code for future comparison against newly generated hash codes.

How It Works

As well as allowing you to store passwords securely (discussed in recipe 11-14), hash codes provide an excellent means of determining if a file has changed. By calculating and storing the cryptographic hash of a file, you can later recalculate the hash of the file to determine if the file has changed in the interim. A hashing algorithm will produce a very different hash code even if the file has been changed only slightly, and the chances of two different files resulting in the same hash code are extremely small.

Warning

Standard hash codes are not suitable for sending with a file to ensure the integrity of the file's contents. If someone intercepts the file in transit, that person can easily change the file and recalculate the hash code, leaving the recipient none the wiser. Recipe 11-17 discusses a variant of the hash code—a keyed hash code—that is suitable for ensuring the integrity of a file in transit.

The HashAlgorithm class makes it easy to generate the hash code of a file. First, instantiate one of the concrete hashing algorithm implementations derived from the HashAlgorithm class. To instantiate the desired hashing algorithm class, pass the name of the hashing algorithm to the HashAlgorithm.Create method, as described in recipe 11-14. See Table 11-4 for a list of valid hashing algorithm names. Then, instead of passing a byte array to the ComputeHash method, you pass a System.IO.Stream object representing the file from which you want to generate the hash code. The HashAlgorithm object handles the process of reading data from the Stream and returns a byte array containing the hash code for the file.

The Code

The example shown here demonstrates the generation of a hash code from a file. The application expects two command-line arguments: the name of the hashing algorithm to use and the name of the file from which the hash is calculated.

using System;
using System.IO;
using System.Security.Cryptography;
namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_15
    {
        public static void Main(string[] args)
        {
            // Create a HashAlgorithm of the type specified by the first
            // command-line argument.
            using (HashAlgorithm hashAlg = HashAlgorithm.Create(args[0]))
            {
                // Open a FileStream to the file specified by the second
                // command-line argument.
                using (Stream file =
                    new FileStream(args[1], FileMode.Open, FileAccess.Read))
                {
                    // Generate the hash code of the file's contents.
                    byte[] hash = hashAlg.ComputeHash(file);

                    // Display the hash code of the file to the console.
                    Console.WriteLine(BitConverter.ToString(hash));
                }

                // Wait to continue.
                Console.WriteLine("
Main method complete. Press Enter.");
                Console.ReadLine();
            }
        }
    }
}

Usage

Running this command:

Recipe11-15 SHA1 Recipe11-15.exe

will display the following hash code to the console:

CA-67-A5-2D-EC-E9-FC-45-AE-97-E9-E1-38-CB-17-86-BB-17-EE-30

In contrast, executing this command:

Recipe11-15 RIPEMD-160 Recipe11-15.exe

will display the following hash code:

E1-6E-FA-BB-89-BA-DA-83-20-D5-CA-EC-FC-3D-52-13-86-B9-41-7C

Verify a Hash Code

Problem

You need to verify a password or confirm that a file remains unchanged by comparing two hash codes.

Solution

Convert both the old and the new hash codes to hexadecimal code strings, Base64 strings, or byte arrays, and compare them.

How It Works

You can use hash codes to determine if two pieces of data (such as passwords or files) are the same, without the need to store or even maintain access to the original data. To determine if data changes over time, you must generate and store the original data's hash code. Later, you can generate another hash code for the data and compare the old and new hash codes, which will show whether any change has occurred. The format in which you store the original hash code will determine the most appropriate way to verify a newly generated hash code against the stored one.

Note

The recipes in this chapter use the ToString method of the class System.BitConverter to convert byte arrays to hexadecimal string values for display. Although easy to use and appropriate for display purposes, you might find this approach inappropriate for use when storing hash codes, because it places a hyphen (-) between each byte value (for example, 4D-79-3A-C9- . . .). In addition, the BitConverter class does not provide a method to parse such a string representation back into a byte array.

Hash codes are often stored in text files, either as hexadecimal strings (for example, 89D22213170A9CFF09A392F00E2C6C4EDC1B0EF9) or as Base64-encoded strings (for example, idIiExcKnP8Jo5LwDixsTtwbDvk=). Alternatively, hash codes may be stored in databases as raw byte values. Regardless of how you store your hash code, the first step in comparing old and new hash codes is to get them both into a common form.

The Code

This following example contains three methods that use different approaches to compare hash codes:

  • VerifyHexHash: This method converts a new hash code (a byte array) to a hexadecimal string for comparison to an old hash code. Other than the BitConverter.ToString method, the .NET Framework class library does not provide an easy method to convert a byte array to a hexadecimal string. You must program a loop to step through the elements of the byte array, convert each individual byte to a string, and append the string to the hexadecimal string representation of the hash code. The use of System.Text.StringBuilder avoids the unnecessary creation of new strings each time the loop appends the next byte value to the result string. (See recipe 2-1 for more details.)

  • VerifyB64Hash: This method takes a new hash code as a byte array and the old hash code as a Base64-encoded string. The method encodes the new hash code as a Base64 string and performs a straightforward string comparison of the two values.

  • VerifyByteHash: This method compares two hash codes represented as byte arrays. The .NET Framework class library does not include a method that performs this type of comparison, and so you must program a loop to compare the elements of the two arrays. This code uses a few time-saving techniques, namely ensuring that the byte arrays are the same length before starting to compare them and returning false on the first difference found.

using System;
using System.Text;

namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_16
    {
        // A method to compare a newly generated hash code with an
        // existing hash code that's represented by a hex code string.
        public static bool VerifyHexHash(byte[] hash, string oldHashString)
        {
            // Create a string representation of the hash code bytes.
            StringBuilder newHashString = new StringBuilder(hash.Length);
// Append each byte as a two-character uppercase hex string.
            foreach (byte b in hash)
            {
                newHashString.AppendFormat("{0:X2}", b);
            }

            // Compare the string representations of the old and new hash
            // codes and return the result.
            return (oldHashString == newHashString.ToString());
        }

        // A method to compare a newly generated hash code with an
        // existing hash code that's represented by a Base64-encoded string.
        private static bool VerifyB64Hash(byte[] hash, string oldHashString)
        {
            // Create a Base64 representation of the hash code bytes.
            string newHashString = Convert.ToBase64String(hash);

            // Compare the string representations of the old and new hash
            // codes and return the result.
            return (oldHashString == newHashString);
        }

        // A method to compare a newly generated hash code with an
        // existing hash code represented by a byte array.
        private static bool VerifyByteHash(byte[] hash, byte[] oldHash)
        {
            // If either array is null or the arrays are different lengths,
            // then they are not equal.
            if (hash == null || oldHash == null || hash.Length != oldHash.Length)
                return false;

            // Step through the byte arrays and compare each byte value.
            for (int count = 0; count < hash.Length; count++)
            {
                if (hash[count] != oldHash[count]) return false;
            }

            // Hash codes are equal.
            return true;
        }
    }
}

Ensure Data Integrity Using a Keyed Hash Code

Problem

You need to transmit a file to someone and provide the recipient with a means to verify the integrity of the file and its source.

Solution

Share a secret key with the intended recipient. This key would ideally be a randomly generated number, but it could also be a phrase that you and the recipient agree to use. Use the key with one of the keyed hashing algorithm classes derived from the System.Security.Cryptography.KeyedHashAlgorithm class to create a keyed hash code. Send the hash code with the file. On receipt of the file, the recipient will generate the keyed hash code of the file using the shared secret key. If the hash codes are equal, the recipient knows that the file is from you and that it has not changed in transit.

How It Works

Hash codes are useful for comparing two pieces of data to determine if they are the same, even if you no longer have access to the original data. However, you cannot use a hash code to reassure the recipient of data as to the data's integrity. If someone could intercept the data, that person could replace the data and generate a new hash code. When the recipient verifies the hash code, it will seem correct, even though the data is actually nothing like what you sent originally.

A simple and efficient solution to the problem of data integrity is a keyed hash code. A keyed hash code is similar to a normal hash code (discussed in recipes 11-14 and 11-15); however, the keyed hash code incorporates an element of secret data—a key—known only to the sender and the receiver. Without the key, a person cannot generate the correct hash code from a given set of data. When you successfully verify a keyed hash code, you can be certain that only someone who knows the secret key could generate the hash code.

Warning

The secret key must remain secret. Anyone who knows the secret key can generate valid keyed hash codes, meaning that you would be unable to determine whether someone else who knew the key had changed the content of a document. For this reason, you should not transmit or store the secret key with the document whose integrity you are trying to protect.

Generating keyed hash codes is similar to generating normal hash codes. The abstract class System.Security.Cryptography.KeyedHashAlgorithm extends the class System.Security.Cryptography.HashAlgorithm and provides a base class from which all concrete keyed hashing algorithm implementations must derive. The .NET Framework class library includes the seven keyed hashing algorithm implementations listed in Table 11-5. Each implementation is a member of the namespace System.Security.Cryptography.

Table 11.5. Keyed Hashing Algorithm Implementations

Algorithm/Class Name

Key Size (in Bits)

Hash Code Size (in Bits)

HMACMD5

Any

128

HMACRIPEMD160

Any

160

HMACSHA1

Any

160

HMACSHA256

Any

256

HMACSHA384

Any

384

HMACSHA512

Any

512

MACTripleDES

128, 192

64

As with the standard hashing algorithms, you can either create keyed hashing algorithm objects directly or use the static factory method KeyedHashAlgorithm.Create and pass the algorithm name as an argument. Using the factory approach allows you to write generic code that can work with any keyed hashing algorithm implementation, but as shown in Table 11-5, MACTripleDES supports fixed key lengths that you must accommodate in generic code.

If you use constructors to instantiate a keyed hashing object, you can pass the secret key to the constructor. Using the factory approach, you must set the key using the Key property inherited from the KeyedHashAlgorithm class. Then call the ComputeHash method and pass either a byte array or a System.IO.Stream object. The keyed hashing algorithm will process the input data and return a byte array containing the keyed hash code. Table 11-5 shows the size of the hash code generated by each keyed hashing algorithm.

The Code

The following example demonstrates the generation of a keyed hash code from a file. The example uses the given class to generate the keyed hash code, and then displays it to the console. The example requires three command-line arguments: the name of the file from which the hash is calculated, the name of the class to instantiate, and the key to use when calculating the hash.

using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;

namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_17
    {
public static void Main(string[] args)
        {
            // Create a byte array from the key string, which is the
            // second command-line argument.
            byte[] key = Encoding.Unicode.GetBytes(args[2]);

            // Create a KeyedHashAlgorithm-derived object to generate the keyed
            // hash code for the input file. Pass the byte array representing the
            // key to the constructor.
            using (KeyedHashAlgorithm hashAlg = KeyedHashAlgorithm.Create(args[1]))
            {
                // Assign the key.
                hashAlg.Key = key;

                // Open a FileStream to read the input file. The file name is
                // specified by the first command-line argument.
                using (Stream file =
                    new FileStream(args[0], FileMode.Open, FileAccess.Read))
                {
                    // Generate the keyed hash code of the file's contents.
                    byte[] hash = hashAlg.ComputeHash(file);

                    // Display the keyed hash code to the console.
                    Console.WriteLine(BitConverter.ToString(hash));
                }
            }

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Usage

Executing the following command:

Recipe11-17 Recipe11-17.exe HMACSHA1 secretKey

will display the following hash code to the console:

2E-5B-9B-2C-91-42-BA-4E-98-DF-39-F6-AE-89-B6-44-61-FB-32-E7

In contrast, executing this command:

Recipe11-17 Recipe11-17.exe HMACSHA1 anotherKey

will display the following hash code to the console:

EF-64-79-3A-3C-A4-44-01-AD-9E-94-2A-B4-58-CF-42-84-3E-27-91

Work with Security-Sensitive Strings in Memory

Problem

You need to work with sensitive string data, such as passwords or credit card numbers, in memory, and you need to minimize the risk of other people or processes accessing that data.

Solution

Use the class System.Security.SecureString to hold the sensitive data values in memory.

How It Works

Storing sensitive data such as passwords, personal details, and banking information in memory as String objects is insecure for many reasons, including the following:

  • String objects are not encrypted.

  • The immutability of String objects means that whenever you change the String, the old String value is left in memory until it is garbage-collected and later overwritten.

  • Because the garbage collector is free to reorganize the contents of the managed heap, multiple copies of your sensitive data may be present on the heap.

  • If part of your process address space is swapped to disk or a memory dump is written to disk, a copy of your data may be stored on the disk.

Each of these factors increases the opportunities for others to access your sensitive data. The .NET Framework includes the SecureString class to simplify the task of working with sensitive string data in memory.

You create a SecureString as either initially empty or from a pointer to a character (char) array. Then you manipulate the contents of the SecureString one character at a time using the methods AppendChar, InsertAt, RemoveAt, and SetAt. As you add characters to the SecureString, they are encrypted using the capabilities of the Data Protection API.

Note

The SecureString class uses features of Data Protection API (DPAPI) and is available only on Windows 2000 SP3 and later operating system versions.

The SecureString class also provides a method named MakeReadOnly. As the name suggests, calling MakeReadOnly configures the SecureString to no longer allow its value to be changed. Attempting to modify a SecureString marked as read-only results in the exception System.InvalidOperationException being thrown. Once you have set the SecureString to read-only, it cannot be undone.

The SecureString class has a ToString method, but this does not retrieve a string representation of the contained data. Instead, the class System.Runtime.InteropServices.Marshal implements a number of static methods that take a SecureString object; decrypts it; converts it to a binary string, a block of ANSI, or a block of Unicode data; and returns a System.IntPtr object that points to the converted data.

At any time, you can call the SecureString.Clear method to clear the sensitive data, and when you have finished with the SecureString object, call its Dispose method to clear the data and free the memory. SecureString implements System.IDisposable.

Note

Although it might seem that the benefits of the SecureString class are limited, because there is no way in Windows Forms applications to get such a secured string from the GUI without first retrieving an unsecured String through a TextBox or another control, it is likely that third parties and future additions to the .NET Framework will use the SecureString class to handle sensitive data. This is already the case in System.Diagnostics.ProcessStartInfo, where using a SecureString, you can set the Password property to the password of the user context in which the new process should be run.

The Code

The following example reads a username and password from the console and starts Notepad.exe as the specified user. The password is masked on input and stored in a SecureString in memory, maximizing the chances of the password remaining secret.

using System;
using System.Security;
using System.Diagnostics;

namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_18
    {
        public static SecureString ReadString()
        {
            // Create a new emtpty SecureString.
            SecureString str = new SecureString();

            // Read the string from the console one
            // character at a time without displaying it.
            ConsoleKeyInfo nextChar = Console.ReadKey(true);
// Read characters until Enter is pressed.
            while (nextChar.Key != ConsoleKey.Enter)
            {
                if (nextChar.Key == ConsoleKey.Backspace)
                {
                    if (str.Length > 0)
                    {
                        // Backspace pressed, remove the last character.
                        str.RemoveAt(str.Length - 1);

                        Console.Write(nextChar.KeyChar);
                        Console.Write(" ");
                        Console.Write(nextChar.KeyChar);
                    }
                    else
                    {
                        Console.Beep();
                    }
                }
                else
                {
                    // Append the character to the SecureString and
                    // display a masked character.
                    str.AppendChar(nextChar.KeyChar);
                    Console.Write("*");
                }
                // Read the next character.
                nextChar = Console.ReadKey(true);
            }

            // String entry finished. Make it read-only.
            str.MakeReadOnly();
            return str;
        }

        public static void Main()
        {
            string user = "";

            // Get the username under which Notepad.exe will be run.
            Console.Write("Enter the user name: ");
            user = Console.ReadLine();

            // Get the user's password as a SecureString.
            Console.Write("Enter the user's password: ");
            using (SecureString pword = ReadString())
            {
// Start Notepad as the specified user.
                ProcessStartInfo startInfo = new ProcessStartInfo();

                startInfo.FileName = "notepad.exe";
                startInfo.UserName = user;
                startInfo.Password = pword;
                startInfo.UseShellExecute = false;

                // Create a new Process object.
                using (Process process = new Process())
                {
                    // Assign the ProcessStartInfo to the Process object.
                    process.StartInfo = startInfo;

                    try
                    {
                        // Start the new process.
                        process.Start();
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("

Could not start Notepad process.");
                        Console.WriteLine(ex);
                    }
                }
            }

            // Wait to continue.
            Console.WriteLine("

Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Encrypt and Decrypt Data Using the Data Protection API

Problem

You need a convenient way to securely encrypt data without the headache associated with key management.

Solution

Use the ProtectedData and ProtectedMemory classes of the System.Security.Cryptography namespace to access the encryption and key management capabilities provided by the Data Protection API (DPAPI).

How It Works

Given that the .NET Framework provides you with well-tested implementations of the most widely used and trusted encryption algorithms, the biggest challenge you face when using cryptography is key management, namely the effective generation, storage, and sharing of keys to facilitate the use of cryptography. In fact, key management is the biggest problem facing most people when they want to securely store or transmit data using cryptographic techniques. If implemented incorrectly, key management can easily render useless all of your efforts to encrypt your data.

DPAPI provides encryption and decryption services without the need for you to worry about key management. DPAPI automatically generates keys based on Windows user credentials, stores keys securely as part of your profile, and even provides automated key expiry without losing access to previously encrypted data.

Note

DPAPI is suitable for many common uses of cryptography in Windows applications, but will not help you in situations that require you to distribute or share secret or public keys with other users.

The .NET Framework contains two classes in System.Security.dll that provide easy access to the encryption and decryption capabilities of DPAPI: ProtectedData and ProtectedMemory. Both classes allow you to encrypt a byte array by passing it to the static method Protect, and decrypt a byte array of encrypted data by passing it the static method Unprotect. The difference in the classes is in the scope that they allow you to specify when you encrypt and decrypt data.

Warning

You must use ProtectedData if you intend to store encrypted data and reboot your machine before decrypting it. ProtectedMemory will be unable to decrypt data that was encrypted before a reboot.

When you call ProtectedData.Protect, you specify a value from the enumeration System.Security.Cryptography.DataProtectionScope. The following are the possible values:

  • CurrentUser, which means that only code running in the context of the current user can decrypt the data

  • LocalMachine, which means that any code running on the same computer can decrypt the data

When you call ProtectedMemory.Protect, you specify a value from the enumeration System.Security.Cryptography.MemoryProtectionScope. The possible values are as follows:

  • CrossProcess, which means that any code in any process can decrypt the encrypted data

  • SameLogon, which means that only code running in the same user context can decrypt the data

  • SameProcess, which means that only code running in the same process can decrypt the data

Both classes allow you to specify additional data (entropy) when you encrypt your data. Entropy makes certain types of cryptographic attacks less likely to succeed. If you choose to use entropy when you protect data, you must use the same entropy value when you unprotect the data. It is not essential that you keep the entropy data secret, so it can be stored freely without encryption.

The Code

The following example demonstrates the use of the ProtectedData class to encrypt a string entered at the console by the user. Note that you need to reference the System.Security.dll assembly.

using System;
using System.Text;
using System.Security.Cryptography;

namespace Apress.VisualCSharpRecipes.Chapter11
{
    class Recipe11_19
    {
        public static void Main()
        {
            // Read the string from the console.
            Console.Write("Enter the string to encrypt: ");
            string str = Console.ReadLine();

            // Create a byte array of entropy to use in the encryption process.
            byte[] entropy = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };

            // Encrypt the entered string after converting it to
            // a byte array. Use LocalMachine scope so that only
            // the current user can decrypt the data.
            byte[] enc = ProtectedData.Protect(Encoding.Unicode.GetBytes(str),
                entropy, DataProtectionScope.LocalMachine);

            // Display the encrypted data to the console.
            Console.WriteLine("
Encrypted string = {0}",
                BitConverter.ToString(enc));

            // Attempt to decrypt the data using CurrentUser scope.
            byte[] dec = ProtectedData.Unprotect(enc,
                entropy, DataProtectionScope.CurrentUser);

            // Display the data decrypted using CurrentUser scope.
            Console.WriteLine("
Decrypted data using CurrentUser scope = {0}",
                Encoding.Unicode.GetString(dec));
// Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.123.160