Chapter 5 — Security Considerations

 

patterns & practices Developer Center

Smart Client Architecture and Design Guide

David Hill, Brenton Webster, Edward A. Jezierski, Srinath Vasireddy and Mohammad Al-Sabt, Microsoft Corporation; Blaine Wastell, Ascentium Corporation; Jonathan Rasmusson and Paul Gale, ThoughtWorks; and Paul Slater, Wadeware LLC

June 2004

Related Links

Microsoft® patterns & practices Library https://msdn.microsoft.com/en-us/practices/default.aspx

Application Architecture for .NET: Designing Applications and Serviceshttps://msdn.microsoft.com/en-us/library/ms954595.aspx

Summary: This chapter covers the issues of smart client security. Smart clients distribute logic and data to the client computer; therefore, the security concerns are different from those associated with thin a client application, where data and logic are confined more to the server. This chapter discusses data security, authentication, authorization, and the role of code access security within a smart client application.

Contents

Authentication
Authorization
Input Validation
Handling Sensitive Data
Code Access Security
Summary

Smart clients are distributed applications that often span several different products and technologies. Managing security in these applications can be very challenging. At the server, you need to adopt an approach of securing the network, the server itself, and then the application. At the client, you should concentrate on using the security features of the platform (including the operating system and the Microsoft® .NET Framework), the privileged operations that the client code can perform (code access security), and the interactions with the server platform (domain) and server application.

Effective security depends on a defense-in-depth approach. As you secure your smart clients, it is very important to consider all aspects of security, including the following:

  • Authentication. This uniquely identifies the user of the client application so that only approved users can access all or part of the application.
  • Authorization. This determines the operations that the uniquely identified user can perform. These operations could be tasks or operations on resources to which the authenticated user is granted access.
  • Data validation. This ensures that only appropriate and valid data is accepted by the application. If you permit any user input without first validating the data, an attacker can compromise the application by injecting malicious input.
  • Protecting sensitive data. This means ensuring that sensitive data (such as passwords or confidential business data) stored and transmitted by the application is secure. Encrypting sensitive data ensures that data is not available in plain text; depending on choice of algorithm, it can also ensure that the information is not tampered with to maintain integrity.
  • Auditing and logging. This involves keeping a record of events and user actions. You should consider logging key user actions or activities on the server or logging them securely on the client, because logs on client computers could be tampered or cleared.
  • Exception management. This ensures that the application deals with exceptions appropriately, failing gracefully and returning user-friendly, non-sensitive information. Exception details can be logged to the event log or application log.
  • Change and configuration management. This ensures that you keep track of the configuration of your IT environment and any changes that occur to it. Doing so allows you to see if unauthorized change occurs and determine the security implications of any authorized changes.

This chapter addresses in detail some of the key security challenges you will face as you design your smart client applications, specifically focusing on authentication, authorization, data validation, and securing sensitive data. The chapter also covers code access security, a key technology in the .NET Framework to manage security at code level rather than user level.

Another important aspect you need to consider when examining smart client security is how your smart client is deployed. For more information about the security issues affecting deployment, see Chapter 7: Deploying and Updating Smart Clients.

**Note   **Any code you use in your application should be analyzed with FxCop. This tool allows you to check managed code assemblies for conformance to the .NET Framework design guidelines, including a base level of security compliance. FxCop can be downloaded off MSDN at https://msdn.microsoft.com/en-us/library/bb429476(VS.80).aspx.

Authentication

Authentication is the process of uniquely identifying a user by verifying his or her credentials. User authentication may be required when the user attempts to run or install the application or when the application makes a connection to a remote service or accesses locally held data.

This section examines some authentication scenarios common to smart clients, covers some of the different ways in which you can make authenticated network calls, and discusses how to gather user credentials and validate those credentials when offline.

Smart Client Authentication Scenarios

Depending on the style and functionality of your smart client application, you may need to authenticate the user at one or more points during the user's interaction with the application. There are four points at which you might choose to authenticate the user:

  • When the application is installed
  • When the application is run
  • When the user accesses sensitive locally held data
  • When the user accesses external services over the network

Authenticated Installation

If your application is centrally deployed (for example, using no-touch deployment), you may need to secure the application on the Web server so that it can be installed only by authorized users. These users must first be authenticated by the Web server, which checks to see if they are authorized to access the application and download it to their client computers.

Securing access to a no-touch deployed smart client application is similar to securing any other Web server located artifact, such as a Web page. Microsoft Internet Information Services (IIS) provides a number of authentication mechanisms, such as Integrated Windows, digest, or basic authentication.

**Note   **Digest and basic authentication are not suitable if you are using no-touch deployment and your application uses a configuration file to store its configuration settings, because the configuration file cannot be downloaded automatically by the .NET Framework using these mechanisms.

After you select the appropriate authentication mechanism, and IIS can identify the user from his or her credentials, you can secure the application and its dependent assemblies by setting file permissions on the application and assembly files. To ease the management of large numbers of users, you may consider providing access to a Microsoft Windows® group (for instance, SmartClientAppUsers) and putting individual users into that group.

All users that you need to authenticate must have a Windows identity on the server so that IIS can secure access to the application and its assemblies, but they do not necessarily need to be logged on to their client computers using this identity. If the user's logon account is not known on the server side, the user is prompted for a user name and password when he or she clicks the link to the application's executable file.

If you use Integrated Windows authentication, the credentials of the logged on user are automatically used to try and gain access to the application. This allows for seamless but secure access when the user is logged on with an identity common to both the client and server.

Authenticated Application Access

Authenticating users to install an application ensures that only authenticated and authorized users are able to run your application from a central location. However, because the application and its dependent artifacts may have been cached on the client computer, you cannot rely on this mechanism to authenticate the user every time the application runs. In this case, or when the application is intentionally deployed locally, you need to carefully consider how user authentication is to be carried out by your application. You may need to authenticate users each time they run the application, particularly if your application provides sensitive functionality, or if you need to be able to revoke a user's authorization to run the application at any time.

In cases where the user is logged on to the client computer using an identity that is common to both the client and server, you may be able to rely on the fact that the user was able to log on to the client computer as sufficient authentication to run the application. This approach allows you to use the Microsoft Windows operating system to provide user authentication, obviating the need to implement it in your code. Also, because Windows can cache user credentials when offline, you do not need to cache them yourself.

For client computers for which you do not have any control over user access, such as those outside your organization's intranet, you may need to adopt a custom authentication mechanism to gather the user's credentials and authenticate them against a remote security authority. If the application is capable of operating offline, you need to cache valid credentials on the client so you can reauthenticate the user against them when he or she starts the application. You should enforce online reauthentication periodically to prevent unlimited use of such applications.

Authenticated Local Data Access

A smart client application often caches data that it has obtained from a remote service (for example, to improve responsiveness or to allow offline capabilities). If the data is sensitive, you might need to consider authenticating the user before granting access to it. In this case, you might choose to allow unauthenticated users to run the application but require users to be fully authenticated and authorized before giving them access to sensitive data.

**Note   **It is important to ensure that only data that the user is authorized to access be cached locally. If the data is sensitive, you also need to ensure that adequate measures are taken to guarantee its security. For details, see "Handling Sensitive Data" later in this chapter.

Locally held data should be held in a secure location and encrypted. Regardless of how users are authenticated, you typically want to use their credentials in some way to access and decrypt the data.

You may be able to use the default credentials that were used to log on to the client computer, or you may need to obtain custom credentials to authenticate the user against a remote security authority. The former possibility is most suitable for applications running in an intranet situation, while the latter is suitable for applications running in an Internet or extranet situation where the users are typically not in the same domain as the remote services they access. One of the benefits of using Integrated Windows authentication is that the operating system authenticates the user, secures the application and local data, and can cache the user's credentials when the user is offline.

For more information about caching sensitive data locally, see "Handing Sensitive Data" later in this chapter.

Authenticated Network Access

You might choose to enable anonymous access to the application and allow any user to download and run it. However, after the application is running on the client, it often needs to access remote services over the network, such as a Web service, to obtain data and services.

Access to network data and services often needs to be secured to prevent unauthorized access. There are many ways in which you might secure remote service access, but usually you need to pass the user's credentials to the remote service so that it can perform user authentication.

Authenticating users when they access remote services over the network is an important issue. Some of the options for ensuring authenticated network service calls are described more fully in "Network Access Authentication Types" later in this chapter.

Choosing the Right Authentication Model

The previous section described the four stages at which you might choose to authenticate the user. You might choose to authenticate the user at one or more of these points, depending on the nature of your application and its functionality. It is important to choose the right point to help ensure that your application and data remain secure, while minimizing any impact on the usability of your application.

If your application is centrally deployed (for example, if it is deployed using no-touch deployment or is deployed to a file share), you might choose to restrict access to users who are authorized. If you want your application to be available to anyone who wants to use it, authenticating the user when the application is installed is not required.

Client computers are generally not physically secure and may even be publicly accessible. If this is the case, and your application provides sensitive functionality, you often need to authenticate the user when the application runs. If your application provides generic functionality that is available to anonymous users, you do not need to authenticate the user at this point. However, you might choose to provide a portion of your application's functionality to anonymous users but require authentication before allowing them to access more restricted functionality.

Securing access to locally held sensitive data is critically important. If your application is deployed to a device that is not physically secure or is accessible to the public, sensitive data should be secured and should be accessible only by authenticated and authorized users. Your application might provide generic functionality to anonymous users but require user authentication when users try to access the sensitive data.

Using Integrated Windows authentication also has benefits when the application runs offline. In this case, Windows caches the user credentials so that the user is authenticated when he or she logs on to the offline client computer. This behavior obviates the need for your client to authenticate the user if you need user authentication when the application runs or accesses locally held sensitive data.

Network Access Authentication Types

There are many different technologies that you can use to authenticate users when accessing remote services, including:

  • Integrated Windows authentication.
  • HTTP basic authentication.
  • HTTP digest authentication.
  • Certificate-based authentication.
  • Web Services Enhancements (WSE)-based authentication.
  • Custom authentication.

Integrated Windows Authentication

With Integrated Windows authentication, the user is authenticated by logging on to his or her computer using a valid Windows account. The credentials could be a local account (an account local to the computer) or a domain account (a valid member of a Windows domain). The identity established during logon can be transparently used by your application to access resources for the entire duration of the user's session. This means that your applications can provide seamless access to network resources, such as Web services, in a secure way without having to prompt the user for additional, or repeated, credentials.

**Note   **Access to the network resources is seamless only if the network resources also use Integrated Windows authentication.

Integrated Windows authentication uses one of two authentication protocols: Kerberos or NTLM. These technologies do not pass a user name and password combination across the network to authenticate or validate the user. As a result, your application or infrastructure does not have to provide additional security to manage credentials during transit.

Client applications that rely on Windows security use an implementation of the IIdentity interface named WindowsIdentity.

**Note   **The .NET Framework also provides the closely related IPrincipal interface. For more details about IIdentity and IPrincipal interfaces, see "Authorization" later in this chapter.

Web Services that Use Integrated Windows Authentication

For Web services that are configured for Integrated Windows authentication, the client application can supply currently logged-on user credentials for authentication purposes before making Web service calls. When you add a reference to a Web service in your application from within the Microsoft Visual Studio® .NET development system, a proxy class is automatically generated and added to your project to programmatically access the Web service. The following code illustrates how to set the credentials of the user who is currently logged on.

MyService service = new MyService();  // A proxy for a web service.
service.Credentials = CredentialCache.DefaultCredentials;
service.SomeServiceMethod();          // Call the web service.

In this case, the DefaultCredentials uses the security context in which the application is running, which is usually the Windows credentials (user name, password, and domain) of the user running the application.

HTTP Basic Authentication

HTTP basic authentication is provided by IIS. With basic authentication, IIS prompts users for a valid Windows account and password. This combination is passed from the client to the server as encoded plain text and is used to authenticate the user at the Web server.

**Note   **To secure basic authentication, you need to secure the communication channel between the client and the server (for example, by enabling Secure Sockets Layer [SSL] on the server) to ensure that the user name/password combination is encrypted and cannot be tampered with or intercepted when in transit. You also need to secure the passwords located on the server. However, SSL can secure communication only between two defined endpoints. If you require secure communication between more than two endpoints, you need to use message-based security.

Web Services that Use Basic Authentication

For a client application interacting with a Web service configured for basic authentication, the client can accept valid user credentials using a logon dialog box and use it for authentication. The following code illustrates how to set the credentials of the user to the Web service proxy expecting basic authentication.

CredentialCache cache = new CredentialCache();
cache.Add( new Uri( service.Url ),     // Web service URL.
   "Basic",                    // Basic Authentication.
   new NetworkCredential( userName, password, domain ) );
service.Credentials = cache;

In this case, userName, password, and domain are accepted as part of the logon dialog box.

HTTP Digest Authentication

HTTP digest authentication offers the same features as HTTP basic authentication but involves a different way of transmitting the authentication credentials. The authentication credentials are converted in a one-way process referred to as hashing. The result of this process is called a hash, or message digest, and it is not feasible to decrypt it using current technologies.

Digest authentication occurs in the following way:

  1. The server sends the browser certain information that will be used in the authentication process.
  2. The browser adds this information to its user name and password, along with some other information, and hashes it. The additional information helps to prevent someone from copying the hash value and using it over again.
  3. The resulting hash is sent over the network to the server along with the additional information in clear text.
  4. The server adds the additional information to a plain text copy it has of the client's password and hashes all of the information.
  5. The server compares the hash value it received with the one it just made.
  6. Access is granted only if the two values are identical.

The additional information is added to the password before hashing so that nobody can capture the password hash and use it to impersonate the true client. Values are added that help to identify the client, the client's computer, and the realm, or domain, the client belongs to. A time stamp is also added to prevent a client from using a password after the password has been revoked.

Because digest authentication sends the password over the network in hashed form, it is clearly preferable to basic authentication, especially if you use basic authentication without encrypting the communication channel. Therefore, you should use digest authentication instead of basic authentication whenever possible.

**Note   **As with basic authentication, digest authentication completes only if the domain server for which a request is made has a plain-text copy of the requesting user's password. Because the domain controller has plain-text copies of passwords, you must ensure that this server is secured from both physical and network attacks.

Certificate-based Authentication

Certificates can enable client and server applications to authenticate each other or to establish a secure connection using digital keys installed on the computer. The client application can use client certificates to identify itself to the server, just as the server can identify itself to the client using a server certificate. A mutually trusted third party, called a certificate authority, can confirm the identity of the certificates. Client certificates can be mapped to Microsoft Windows accounts in Microsoft Active Directory® directory service.

You can set up a site so that users without certificates are logged on as guests, but users with certificates are logged on as the user to which his or her certificate maps. You can then customize the site based on the certificate.

If you need to authenticate individual users, you can use a technique known as one-to-one mapping where a certificate is mapped to an individual account. If you need to authenticate all of the users from a particular group or organization, you can use many-to-one mapping where, for example, any certificate containing a common company name is mapped to a single account.

In certificate-based authentication, client applications use certificates that can be authenticated by Web services. In this case, the client application digitally signs the SOAP messages using X.509 certificates to ensure that the message is from a trusted source and is not altered during transit before it reaches the designated Web service.

One major consideration of certificate-based authentication is how to manage situations when a certificate should no longer be valid. For example, if an employee uses a certificate to be authenticated and that employee is then dismissed, the certificate should no longer allow the user to access resources. Therefore, it is important that your certificate security infrastructure includes the administration of certificate revocation lists. These lists are present on the server and should be checked each time the client connects to a network resource.

Server-based revocation lists cannot be checked when a smart client goes offline, so there is potential for a user to access resources locally on the client that he or she should not be able to access at the server. To help get around this problem, you may choose to have relatively short lease times on your client certificates. Short lease times force the client to regularly connect to a certificate server and verify that the certificate has not been revoked prior to renewing the lease and allowing connection to the server side of the application.

For more information, see "About Certificates" at https://technet.microsoft.com/en-us/library/cc700805.aspx.

WSE-based Authentication

You can programmatically sign the SOAP messages to a Web service using Web Services Enhancements version 2.0. WSE 2.0 is an implementation that supports emerging Web services standards such as WS-Security, WS-SecureConversation, WS-Trust, WS-Policy, WS-Addressing, WS-Referral, and WS-Attachments and Direct Internet Message Encapsulation (DIME). WSE provides a programming model to implement various specifications that it supports.

Client applications that use WSE can use one of the Find methods (for example, FindCertificateByHash or FindCertificateByKeyIdentifier) on the X509CertificateStore class to programmatically select a certificate from the store, create a digital signature using the certificate, add it to the WS-Security SOAP header, and call the Web service. Alternatively, the client application can also open the certificate store of the currently logged-on user as shown in the following code example.

X509CertificateStore store;
store = X509CertificateStore.CurrentUserStore( X509CertificateStore.MyStore );
bool open = store.OpenRead();

For more information, see "Web Services Enhancements" at https://msdn.microsoft.com/en-us/netframework/aa663324.aspx.

For more information about using client certificates, see "Signing a SOAP Message Using an X.509 Certificate" in the WSE 2.0 documentation.

Custom Authentication

In some cases, the standard authentication options provided by Windows are not appropriate for your applications, and you will need to design your own form of authentication. Fortunately, the .NET Framework provides options to help you design a custom authentication solution.

The .NET Framework supports an implementation of IIdentity, called GenericIdentity. You can use GenericIdentity, or create your own custom identity class. Designing a custom authentication solution can be difficult, because you have to take your own steps to ensure that the method is secure. You may also have to maintain a separate store for your identities.

Gathering and Validating User Credentials

Whatever form of authentication you use, you need to gather user credentials that can then be validated. For users that are already logged on using Integrated Windows authentication, you may just need to gather the existing credentials, and for a custom authentication solution, you may need to gather credentials securely through your own logon dialog box.

**Note   **Do not store user credentials in your code for longer than is necessary. In particular, do not store credentials in global variables, which provide access to them through publicly accessible methods or properties, and do not save them to disk.

Gathering Currently Logged-On User Credentials

If you are using Integrated Windows authentication, your users log on at the start of their Windows session. Your applications can then use this information to ensure that they have the appropriate credentials to run.

The following code demonstrates the basic functionality.

using System.Security.Principal;

// Get principal of the currently logged in user.
WindowsPrincipal wp = new WindowsPrincipal( WindowsIdentity.GetCurrent() );

// Display the current user name.
label1.Text = "User:" + wp.Identity.Name;

// Determine if user is part of a windows group.
if( wp.IsInRole( "YourDomain\\YourWindowsGroup" ) )
{
    // Is a member.
}
else
{
    // Is not a member.
}

Gathering User Credentials Using a Logon Dialog Box

If you are designing your own logon dialog box to accept credentials from the user, you need to take a number of measures to ensure that you meet the security requirements of your organization (such as enforcing strong password policy and having passwords expire at periodic intervals). Consider the following guidelines when you design your logon dialog box:

  • Do not blindly trust user input. If you do so, a malicious user can compromise your application. For example, an application that uses input with no validation to dynamically construct SQL code can be vulnerable to SQL injection attacks.

  • Check for type, format, or range of input data. Consider using regular expressions to do these checks. Using regular expressions enables you to check for type (for example, string or integer), format (for example, enforcing password policy requirements such as use of numbers, special characters, and a mix of lowercase and uppercase characters), and range (for example, a user name with a minimum of 6 characters and maximum of 25 characters).

    The following code example enforces a password between 8 and 10 characters long with a combination of uppercase, lowercase, and numeric characters.

    // Validate the user supplied password.
    if( !Regex.Match( textBox1.Text,
            @"^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$",
            RegexOptions.None ).Success )
    {
        // Invalid email address.
    }
    
  • When designing a dialog box with a password field text box, ensure that the PasswordChar property is set to a character that is displayed when text is entered in the control, as shown in the following example.

    // The password character is set to asterisk.
    textBox1.PasswordChar = '*';
    

Authentication Guidelines

When designing authentication for your applications, you should consider the following guidelines:

  • Determine where authentication needs to occur during the user's interaction with your application.
  • Consider using Integrated Windows authentication to authenticate users as they log on to the client and before they can access your application, its data, and any remote service.
  • If your application is centrally deployed and you need to restrict access to only authorized users, authenticate users when the application runs using one of the authentication mechanisms provided by IIS.
  • If your application provides sensitive functionality or access to sensitive locally held data, ensure that users are properly authenticated before allowing access.
  • If your application requires custom authentication, ensure your application enforces a strong user name and password policy. As a general practice, you should require a minimum of 8 characters and a mixture of uppercase and lowercase characters, numbers, and special characters.
  • Require user authentication for access to remote services over the network if they provide sensitive functionality or access to sensitive data.
  • Ensure that user credentials are not transmitted unprotected over the network. Some forms of authentication avoid passing user credentials over the network at all, but if they must be transmitted, you should ensure that they are encrypted, or sent over a secure connection.

For more information, see "Authentication" in "Chapter 4 – Design Guidelines for Secure Web Applications" of Improving Web Application Security: Threats and Countermeasures at https://msdn.microsoft.com/en-us/library/aa302420.aspx.

Authorization

After users are authenticated, you can determine what they have access to within the system by using authorization. Authorization is confirmation that an authenticated user has permission to perform an operation. Authorization governs the resources (for example, files and databases) that an authenticated user can access and the operations (for example, changing passwords or deleting files) that an authenticated user can perform. Users who are not authenticated (that is, anonymous users) are not able to be specifically authorized and need to be assigned a default set of permissions.

A number of factors determine exactly how you perform authorization in your environment. You need to determine whether to manage authorization based on application functionality or system resources. You need to decide whether to perform fine-grained authorization within methods or to perform checks at the method level. You also need to determine where the user information required for authorization is stored (for example, in Active Directory or a Microsoft SQL Server™ database). If you are going to allow your smart clients to work offline, you need a strategy for authorization of offline clients.

The .NET Framework provides the IPrincipal interface, which is used in conjunction with the IIdentity interface to define properties and methods to manage the security context of running code. Two implementations of this interface are also provided: WindowsPrincipal and GenericPrincipal. Client applications that use Integrated Windows authentication use WindowsPrincipal, whereas client applications that use custom authentication use GenericPrincipal.

Types of Authorization

Two methods of authorization are commonly used in the Windows operating system: resource-based authorization and role-based authorization. Resource-based authorization relies on access control lists (ACLs), and role-based authorization performs authorization based on user roles.

Resource-based Authorization

For resource-based authorization, you can attach discretionary access control lists (DACLs) to securable objects. The system then makes access decisions by comparing the group memberships in a token to the contents of the ACL to determine whether the user has the requested access. The ACL model is ideal for many types of applications. However, it is not appropriate for all situations. For example, you may need to make access decisions based on business logic or on nonpersistent objects that are created when needed.

Role-based Authorization

Role-based authorization allows you to associate users and groups with the permissions that they need to do their jobs. When a user or group is added to a role, the user or group automatically inherits the various security permissions. These could be permissions to perform actions or to access various resources. Figure 5.1 shows the relationship between roles and permissions in role-based authorization.

Ff648203.auth0101(en-us,PandP.10).gif

Figure 5.1   Role-based authorization

In Microsoft Windows 2000 Server Service Pack 4 (SP4) and Windows Server™ 2003 operating system, role-based authorization is generally administered using Authorization Manager. Authorization Manager is a set of COM-based run-time interfaces, along with a Microsoft Management Console (MMC) snap-in for configuration. Developers can use Authorization Manager to ensure that applications can manage and verify client requests to perform application operations, and application administrators can use it to manage user roles and permissions. With Authorization Manager, you can aggregate low-level operations into groups called Tasks and manage authorization at that level. It also allows you to run custom authorization logic before and after authorization.

One significant advantage of Authorization Manager is that it further abstracts the authorization store from the application requiring authorization, meaning that developers can always communicate with Authorization Manager, regardless of whether the store is in Active Directory or is file-based.

Adding Authorization Capabilities to Your Application

The .NET Framework provides a number of options for adding authorization capabilities to your application. These include:

  • Performing declarative demands using the PrincipalPermissionAttribute.
  • Performing imperative demands using the PrincipalPermission object.
  • Performing role checks using the IsInRole method.
  • Performing role checks for custom authentication.

Performing Declarative Demands Using the PrincipalPermissionAttribute

You can place demands at the class level, or at the member level on individual methods, properties, or events. If you place a declarative demand at both the class and member level, the declarative demand on the member overrides (or replaces) the demand at the class level.

The following code example shows a declarative demand for the PrincipalPermission object.

// Declarative example.
[PrincipalPermissionAttribute( SecurityAction.Demand, Role="Teller" )]
void SomeTellerOnlyMethod()
{
}

Performing Imperative Demands Using the PrincipalPermission Object

You can perform imperative demands by programmatically calling the Demand method of the PrincipalPermission object, as shown in the following code example.

// Programmatic example.
public SomeMethod()
{
    PrincipalPermission permCheck = new PrincipalPermission( null, "Teller" );
    permCheck.Demand();
    // Only Tellers can execute the following code.
    // Non members of the Teller role result in a security exception.
    . . .
}

One advantage of calling the method programmatically is that you can determine if the principal is in more than one role. The .NET Framework does not allow you to do this declaratively. The following code example shows how to perform the check.

// Using PrincipalPermission.
PrincipalPermission permCheckTellers = new PrincipalPermission( null, 
"Teller" );
permCheckTellers.Demand();
PrincipalPermission permCheckMgr = new PrincipalPermission( null, 
"Manager" );
permCheckMgr.Demand();

Performing Role Checks Using the IsInRole Method

You may choose to access the values of the principal object directly and perform checks without a PrincipalPermission object. In this case, you can read the values of the current thread's principal or use the IsInRole method to perform authorization, as shown in the following code example.

// Using IsInRole.
if ( Thread.CurrentPrincipal.IsInRole( "Teller" ) &&
     Thread.CurrentPrincipal.IsInRole( "Manager" ) )
{
    // Perform privileged operation.
}

Performing Role Checks for Custom Authentication

If your application is not Windows-based, you can programmatically populate a GenericPrincipal object with a set of roles obtained from a custom authentication data store such as a SQL Server database, as shown in the following code example.

GenericIdentity userIdentity = new GenericIdentity( "Bob" );
// Typically role names would be retrieved from a custom data store.
string[] roles = new String[]{ "Manager", "Teller" };
GenericPrincipal userPrincipal = new GenericPrincipal( userIdentity, roles );
if ( userPrincipal.IsInRole( "Teller" ) )
{
    // Perform privileged operation.
}

Authorization Guidelines

Authorization is critical to control user access to application functionality and resources accessed. Improper or weak authorization leads to information disclosure and data tampering. Consider the following authorization guidelines:

  • Use multiple gatekeepers where possible to enforce authorization checks when accessing resources or performing operations. Using client checks combined with checks on the server provides defense in depth to prevent an attack from a malicious user who manages to bypass one of the gatekeepers. Common gatekeepers on the server include IIS Web permissions, NTFS file system permissions, Web service file authorization (which applies only with Windows authentication), and principal permission demands.

  • Authorize access to system resources using the security context of the user. You can use role-based authorization to authorize access based on user identity and role membership. Integrated Windows authentication with Windows ACLs on the secured resources (such as files or the registry) determines whether the caller is allowed to access the resource. For assemblies, you can authorize calling code based on evidence, such as an assembly's strong name or location.

  • Ensure that roles are defined with enough granularity to adequately separate privileges. Avoid granting elevated privileges just to satisfy the requirements of certain users; instead, consider adding new roles to meet those requirements.

  • Use declarative demands rather than imperative demands where possible. Declarative demands provide or deny access to all of the method's functionality. They also work much better with security tools and help with security audits, because tools are able to access these demands by examining the application.

  • If you need to determine if the principal is in more than one role, consider imperative checks using IsInRole method. The .NET Framework version 1.1 does not allow AND checks to be performed declaratively; however, they can be performed programmatically inside the method as shown in the following code example.

    // Checking for multiple roles.
    if ( Thread.CurrentPrincipal.IsInRole( "Teller" ) &&
         Thread.CurrentPrincipal.IsInRole( "Manager" ) )
    {
        // Perform privileged operation.
    }
    
  • Use code access security to authorize calling code access to privileged resources or operations, based on evidence, such as an assembly's strong name or location. For more information, see "Code Access Security" later in this chapter.

Authorizing Functionality When the Client Is Offline

When users are connected to the network, they can be authorized directly against a network authorization store, but when they are not, they may still need to be authorized.

Any form of authorization is only as strong as the authentication mechanism used. If you allow anonymous authentication, you should be particularly careful about what functionality you allow users to access and generally should not authorize users to access system resources.

If you are authenticating users to use an application, you can let Windows act as the sole gatekeeper to determine which resources are available for the logged-on user profile. In this case, the user is often allowed to access local system resources.

You may choose to create different versions of the same application for different roles. When the user is connected to the network, he or she is allowed to install only the version of the application tailored to his or her role. Then, when the user runs the application offline, the correct functionality is automatically provided without the application being connected.

The Authorization and Profile Application Block

Microsoft offers an application block that provides infrastructure to simplify the inclusion of authorization functionality into your application.

The Authorization and Profile Application Block provides an infrastructure for role-based authorization and access to profile information. The block allows you to:

  • Authorize a user of an application or system.
  • Use multiple authorization storage providers.
  • Plug in business rules for action validation.
  • Map multiple identities to a single user, extending the idea of an identity to include authentication methods.
  • Access profile information that can be stored in multiple profile stores.

For more information, see Authorization and Profile Application Block at https://msdn.microsoft.com/en-us/library/cc339241.aspx.

Input Validation

Applications with poor input validation can be compromised by malicious input by an attacker. Validating user input is one of the first lines of defense for your application. Consider the following input validation guidelines for your smart client application:

  • Ensure that your smart client application validates all input before processing or passing it to downstream resources and assemblies.

  • Perform thorough validation of user input data if you are passing it to an unmanaged API. Doing so helps to prevent buffer overflows. You should limit user input of data that is passed to unmanaged APIs.

  • Always validate data obtained from all external sources, such as Web sites and Web services.

  • Never rely on client-side validation of data that is passed to your Web service or Web application. Validate data on the client and then validate it again on the server to prevent malicious input that bypasses client-side validation.

  • Never allow users to enter SQL queries directly. Always provide prepackaged or parameterized queries that are thoroughly reviewed for security problems. Allowing users to enter SQL queries directly introduces the possibility of SQL injection attacks.

  • Constrain and validate user input for known correct values or patterns, rather than for incorrect input. It is easier to check for a finite list of known values than to check for an infinite list of unknown malicious input types. You can either reject the bad input or sanitize it (that is, strip out potentially unsafe characters) before acting on it.

  • Constrain input by validating it for type, length, format, and range. One way to do this is use to regular expressions (available from the System.Text.RegularExpressions namespace) to validate user input.

  • Reject unknown bad data and then sanitize input. If your application needs to accept some user input in free form (for example, comments in a text box), you can sanitize the input as shown in the following example.

    private string SanitizeInput( string input )
    {
        // Example list of characters to remove from input.
        Regex badCharReplace = new Regex( @"([<>""'%;()&])" );
        string goodChars = badCharReplace.Replace( input, "" );
        return goodChars;
    }
    
  • Consider centralizing your validation routines to reduce development effort and aid future maintenance.

For more information, see "Input Validation" in "Chapter 4 – Design Guidelines for Secure Web Applications" of Improving Web Application Security: Threats and Countermeasures at https://msdn.microsoft.com/en-us/library/aa302420.aspx.

Handling Sensitive Data

If you are accustomed to designing Web applications, you understand the importance of securing stored data and data that is in transit. The data you store on a Web server is typically written to a physically secure location that is already well protected to prevent it from being attacked. In smart client applications, you also need to closely consider the data that resides on the client. If such data is sensitive, it is important that it is handled appropriately to ensure its security. To protect data in transit, you can secure the transport layer using SSL and securing the message contents using WS-Security or Message Queuing encryption tools.

Only data to which the user is authorized access should be made available to the client application. If the client application can be used by more than one person on a single computer, the data associated with each individual user should be considered sensitive data, and steps should be taken to ensure that only authorized users can access it.

Sensitive data includes any data that an attacker may find useful to access or modify, either because the information is confidential, or because it can help in an attack. Sensitive data may be data that the server provides to the client, but it can also include application configuration files, local databases, or registry information.

In general, you should try to ensure that sensitive data is not cached locally. However, in the case of a smart client application, you may need to cache this data (for example, to allow for occasionally connected operation by saving the data to a local store for later use).

**Note   **In some cases, sensitive data may be sent to disk as a result of paging from memory. Therefore, you should also consider data that is present in memory when determining what data needs to be encrypted.

Determining Which Data to Store on the Client

By definition, users, and therefore potential attackers, have physical access to clients. Given enough time, attackers are often able to obtain sufficient administrative access to access almost any data, so you should carefully consider what data should be persisted on the client. As a general rule, you should make authorization decisions on the server, so that the only data you pass from the server to the client is data that the user is allowed to access. In addition to improving performance, making authorization decisions on the server also ensures that the data is not available on the client for a potential attacker to access.

You should never store sensitive data in text-based files and should always encrypt the data so that it can be easily accessed only by authorized users. You should avoid using text-based configuration files to store sensitive security information, such as passwords or database connection strings. If this information must be stored locally, you should encrypt the information, store it in a file or registry key, and then restrict access to that object with a DACL. Any persisted data personal to the logged-on user must also be kept private and secure, particularly if the computer is shared between users.

In many cases, more data is stored on the client if the application needs to run offline. However, you should determine whether all of the data is required offline, or whether you want to restrict the user from performing certain actions when offline, so that you do not have to cache sensitive data locally.

In some cases, if data is confidential and can be entered by the user on demand, you may choose not to store it locally on the client at all and instead obtain it from the user as needed.

If your application needs to store sensitive data locally, you should usually avoid using removable storage (such as floppy disks, zip disks, or USB storage devices) or external portable storage to store sensitive data. However, user-specific data can be stored on removable media when you can be sure that the removable media is owned by that user (for example, by using a certificate or a smart card). Thus, user-specific data can be kept in a secure location that travels with the user, so that roaming users can access the application and their data without leaving that data on the local computer.

**Note   **As you consider which sensitive data to store on the client, you should ensure that by storing information about your employees or customers, you are not violating privacy regulations. These laws differ from country to country, so you should familiarize yourself with privacy regulations in the countries where your application is used.

Techniques for Protecting Sensitive Data

For data that you need to store at the client, there are a number of additional measures you can take to prevent unauthorized access. These include the following:

  • Ensure that only authorized users can access data.
  • Consider using EFS to encrypt files.
  • Consider using DPAPI to avoid key management issues.
  • Consider storing hash values instead of plain text.
  • Consider isolated storage for partially trusted applications.
  • Secure your private keys.

Ensure that Only Authorized Users can Access Data

Your data often needs to be protected to help make sure that only authorized users can access it. Depending on the nature of your application and how transient the data is, you may choose to use resource-based security or role-based security to protect your data. For more information, see "Authorization Guidelines" earlier in this chapter.

Consider Using EFS to Encrypt Files

One option for ensuring that files are held securely on smart clients is to use the Encrypting File System (EFS) to encrypt sensitive data files. This solution is not particularly scalable; however, it can be useful for specific files, and it may be useful for situations where you are caching data locally on the client (for example, to enable occasionally connected smart clients).

Consider Using DPAPI to Avoid Key Management Issues

Windows 2000 and later versions of the Windows operating system provide the Win32® Data Protection API (DPAPI) for encrypting and decrypting data. It is part of the Cryptography API (Crypto API) and is implemented in crypt32.dll. It consists of two methods, CryptProtectData and CryptUnprotectData.

DPAPI is particularly useful because it can eliminate the key management problem exposed to applications that use cryptography. While encryption ensures that the data is secure, you must take additional steps to ensure the security of the key. To derive the encryption key, DPAPI uses the password of the user account associated with the code that calls the DPAPI functions. As a result, the operating system (and not the application) manages the key.

DPAPI can work with either the machine store or user store. The user store is automatically loaded based on the logged-on user profile. Your client applications will mostly use DPAPI with the user store, unless there is need to store secrets common across all users who can log on to the computer.

The keys that DPAPI uses to encrypt and decrypt sensitive data are specific to a computer. A different key is generated for each computer, which prevents one server from being able to access data encrypted by another.

The unmanaged DPAPI requires assemblies to have full trust. Applications that have fully trusted and partially trusted assemblies can isolate code with high privileges and enable it to be called from partially trusted code. For more information, see "How To Create a Custom Encryption Permission" at https://msdn.microsoft.com/en-us/library/aa302362.aspx.

Consider Storing Hash Values Instead of Plain Text

Sometimes data is stored so that it can be used to validate user input (for example, a user name and password combination). In such cases, rather than storing the data itself in plain text, you can store a cryptographic hash of the data. Then when the user input is made, that data can also be hashed, and the two hashes can be compared. Storing the hash reduces the risk of the secret being discovered because it is computationally impossible to deduce the original data from its hash, or to generate an identical hash from other data.

Consider Isolated Storage for Partially Trusted Applications

Isolated storage allows your application to save data to a unique data compartment that is associated with some aspect of the code's identity, such as its Web site, publisher, or signature. The data compartment is an abstraction, not a specific storage location; it consists of one or more isolated storage files, called stores, which contain the actual directory locations where data is stored.

Isolated storage can be particularly useful for partially trusted applications that need to store state data specific to particular users and assemblies. Partially trusted applications do not have direct access to the file system to persist state unless they have explicitly been granted permission to do so through a security policy change.

Data stored in isolated storage is isolated and protected from other partially trusted applications, but it is not protected from fully trusted code or from other users who have access to the client computer. To secure data in these scenarios, you should employ data encryption and file system security through the use of DACLs. For more information, see "Introduction to Isolated Storage" in the .NET Framework Developer's Guide at https://msdn.microsoft.com/en-us/library/3ak841sy(VS.71).aspx.

Protect Private Keys

Unprotected private keys are susceptible to a wide range of attacks by malicious users or malicious code. Private keys used to sign assemblies should not be left in insecure locations such as developers' computers or openly shared environments. Stolen private keys can be used by an attacker to sign malicious code with your strong name. You should strongly consider securing your private keys with a central security authority designated for this purpose within your organization. You can also keep your private keys on a physically secure, isolated computer, transferring the keys where necessary using portable media.

For more information about storing secrets effectively, see Writing Secure Code, Second Edition, by Michael Howard and David LeBlanc.

Code Access Security

Code access security is .NET Framework technology that applies authentication and authorization principles to code instead of users. Code access security can be a powerful mechanism for ensuring that only the code that you intended to run is run by the user.

All managed code is subject to code access security. When an assembly is loaded, it is granted a set of code access permissions that determine what resources it can access and what types of privileged operations it can perform. The .NET Framework security system uses evidence to authenticate (identify) code in order to grant these permissions.

**Note   **An assembly is the unit of configuration and trust for code access security. All code in the same assembly receives the same permissions and is therefore equally trusted.

Code access security consists of the following elements:

  • Permissions. Permissions represent the rights for code to access a secured resource or perform a privileged operation. The Microsoft .NET Framework provides codeaccesspermissions and codeidentitypermissions. Code access permissions encapsulate the ability to access a particular resource or perform a particular privileged operation. For example, the FileIOPermission is required before the application can perform any file I/O operations. Code identity permissions are used to restrict access to code, based on an aspect of the calling code's identity, such as its strong name.
  • Permission sets. The .NET Framework defines a number of permission sets, which represent a group of permissions commonly assigned as a whole. For example, the .NET Framework defines the FullTrust permission set, which assigns all permissions to fully trusted code, and the LocalIntranet permission set, which assigns a very limited number of permissions.
  • Evidence. Evidence is used by the .NET Framework security system to identify assemblies. Code access security policy uses evidence to help grant the right permissions to the right assembly. Evidence may be location-related (for example, URL, site, application directory, or zone) or author-related (for example, strong name, publisher, or hash).
  • Policy. Code access security policy is configured by administrators and determines the permissions granted to assemblies. Policy can be established at the enterprise, machine, user, and application domain levels. Each policy is defined in an XML configuration file.
  • Code groups. Each policy file contains a hierarchical collection of code groups. Code groups are used to assign permissions to assemblies. A code group consists of a membership condition (based on evidence) and a permission set. The .NET Framework defines a number of default code groups, such as the Internet, Local Intranet, Restricted, and Trusted zones.

For more detailed information about code access security, see the following chapters of Improving Web Application Security: Threats and countermeasures: "Chapter 7 – Building Secure Assemblies" at https://msdn.microsoft.com/en-us/library/aa302423.aspx and "Chapter 8 – Code Access Security in Practice" at https://msdn.microsoft.com/en-us/library/aa302424.aspx.

Code Access Security Permission Resolution

Code access security uses the steps outlined in Figure 5.2 to determine which permissions are assigned to an assembly.

Ff648203.casf01(en-us,PandP.10).gif

Figure 5.2   Determining which permissions are assigned to an assembly

The following steps outline the procedure in more detail:

  1. An assembly is loaded, and evidence is gathered and presented to the host.
  2. The evidence is evaluated against the security policy for the hosting environment.
  3. The output of this evaluation is a set of permissions granted to the assembly. These permissions define what the assembly can and cannot do in this environment.
  4. When the assembly asks to perform a privileged operation, the demands of that operation are compared with the permissions of the assembly. If the assembly has permission, the code is allowed to perform the operation; otherwise, a security exception is thrown.

Designing for Code Access Security

The permissions assigned to your code depend on the evidence associated with your code and the security policy in place on the client computer. To ensure the security of your application while maintaining its functionality, you need to carefully consider the permissions that your application requires, and the way in which these permissions are granted.

Applications that are granted all permissions (those applications defined in the FullTrust permission set) are known as fully trusted applications. Applications that are not granted all permissions are known as partially trusted applications.

In theory, it is generally preferable to design your applications to be partially trusted. However, smart client applications frequently need to perform a number of operations that partially trusted applications cannot perform by default. These operations include:

  • Accessing servers other than the one from which the application was run or accessing servers that use a different protocol.
  • Accessing the local file system.
  • Accessing and interacting with local Microsoft Office applications.
  • Accessing and interacting with unmanaged code, such as COM objects.

If your smart client is required to perform these kinds of operations, you should consider making it a fully trusted application or granting it the additional specific permissions it requires to operate properly.

**Note   **Applications deployed using no-touch deployment are automatically partially trusted by default. If your smart client needs to perform additional operations that cannot be performed by partially trusted applications, you either need to deploy a new security policy or use an alternative method to deploy the application.

Designing and building partially trusted applications can be challenging, but carefully considering and restricting the permissions granted to your application helps ensure that it cannot perform inappropriate actions or access resources that are not explicitly required.

All code must be granted permission to run before it can be run, but code that accesses secured resources or performs other security-sensitive operations (such as calling unmanaged code or accessing the local file system) must be granted additional permissions by the .NET Framework to be able to function. Such code is referred to as privileged code. Conversely, nonprivileged code does not require access to sensitive resources and requires only permission to run. When you design and build your application and its assemblies, you should identify and document privileged and nonprivileged code. Doing so helps you determine the permissions that your code requires.

You should also carefully examine which evidence is used by the .NET Framework to assign permissions to your code. Evidence based on the location of the application (for example, a file share or Web server) should be considered only if the central location is secure. Similarly, applications whose evidence is based on a common key used to sign all code (for example, by an organization's IT department) should be used only when the key is secure. However, it is generally more secure to rely on strong name evidence rather than any location-based evidence such as a Web server address.

Designing Partially Trusted Applications

Use the following guidelines when you design partially trusted applications:

  • Know your application deployment scenarios.
  • Avoid permissions demands that raise exceptions.
  • Use the Demand/Assert pattern for partially trusted callers.
  • Consider using strong names for your assemblies.
  • Avoid giving full trust to restricted zones.

Know Your Application Deployment Scenarios

You should have a clear understanding of your application deployment scenarios during design, because the location to which your application is deployed has a significant effect on the permissions that the application is granted by default. Application functionalities such as displaying a dialog box (for example, using a SaveFileDialog) or accessing system resources may be restricted based on the deployment location of the application.

In particular, the permissions granted to your application depend on the zone in which it is located (for example, the Internet zone, Local Intranet zone, or Trusted zone). The user has some control over the application's membership in the Trusted zone, but you should not rely on the user to place your application in this zone to ensure correct functionality. You should design your application to fail gracefully if insufficient permissions are granted to it at run time.

If a user attempts to perform an action and the application does not have sufficient permissions to perform the action, the attempt may result in a failed permission demand, which in turn raises a security exception. Your application needs to handle these exceptions or it will fail. You should ensure that such failures are handled gracefully, and you should give the user enough information to address the problem without revealing inappropriate or sensitive security-related information.

**Note   **Applications deployed using the ClickOnce deployment features of the .NET Framework version 2.0 will be granted specific permissions according to their deployment manifest. The granted permissions will be fixed when the application is deployed, and the placement of the application's location in the Trusted zone will not affect the permissions that are granted.

Avoid Permission Demands that Raise Exceptions

Determine the permission required for each of your application functionalities to run properly without raising exceptions. Consider the following:

  • Design a workaround to avoid the permission demand that can cause exceptions. For example, for intranet-based applications, instead of having the application automatically open and read a file from the hard disk, you can use OpenFileDialog to display a dialog box that instructs the user to select the file.

  • Check permissions to gracefully deal with exceptions (specifically, SecurityException). In your code, you can create an instance of a permission class specific to the resource that you are trying to access and check for necessary permissions before accessing the resource. For example, you can use the FileDialogPermission class and the SecurityManager.IsGranted static method to check for permissions when you have to display a dialog box using OpenFileDialog or SaveFileDialog, as follows.

    FileDialogPermission fileDialogPermission = new
                 FileDialogPermission( FileDialogPermissionAccess.Save );
    if ( !SecurityManager.IsGranted( fileDialogPermission ) ) 
    {
        // Not allowed to save file.
    }
    

    **Note   **IsGranted does not guarantee that an operation will succeed because it does not traverse the stack to determine whether all upstream code has the required permissions.

  • Consider prototyping and testing your application scenario for various deployment zones:

    • If your application is designed to run from a file share, you can simulate this scenario by addressing the application as a network share (for example, \\MachineName\c$\YourAppPath\YourApp.exe) and running it from your hard disk.
    • If your application is designed to run from the Web Internet zone, you can use the IP address of your computer (for example, \\<MachineIPaddress\c$\YourAppPath\YourApp.exe) to simulate this scenario.

Use the Demand/Assert Pattern for Partially Trusted Callers

The Demand/Assert pattern is used in fully trusted assemblies to allow access to privileged operations when called by partially trusted callers. This pattern is useful when a partially trusted caller needs to perform privileged operations in secure manner but does not have the necessary privileges. By using Assert, you vouch for the trustworthiness of your code's callers.

**Note   **The Demand/Assert pattern should be used only when you fully understand the security risks that its use can introduce. Asserting permissions turns off the normal .NET Framework permission checks, which check all of the calling code on the stack. Turning off this mechanism may introduce a serious security vulnerability into your code and should only be attempted when you fully understand its implications and have exhausted all other possible solutions.

In this pattern, the Demand calls occur before the Assert calls. The Demand checks to see if the caller has the permission, and then the Assert turns off the stack walk for the particular permission so that callers are not checked by the common language runtime to see they if have appropriate permissions.

For a partially trusted caller to successfully call a fully trusted assembly method, you can demand appropriate permissions to ensure that the partially trusted caller does not harm the system, and then assert the particular permission to perform the high privilege operation.

You should call Assert in your fully trusted assembly prior to making the privileged operation and call RevertAssert afterward to ensure that subsequent code in your method calls does not inadvertently succeed because the Assert is still in effect. You should place this code in a private function so that the Assert is removed from the stack automatically (using a RevertAssert call) after the method returns. It is important to make this method private so that an attacker cannot invoke the method using external code.

Consider the following example.

Private void PrivilegedOperation()
{
    // Demand for permission.
    new FileIOPermission( PermissionState.Unrestricted ).Demand();
    // Assert to allow caller with insufficient permissions.
    new FileIOPermission( PermissionState.Unrestricted ).Assert();
    // Perform your privileged operation.
}

By default, a fully trusted assembly does not allow calls from partially trusted applications or assemblies; such calls raise a security exception. To avoid these exceptions, you can add AllowPartiallyTrustedCallersAttribute (APTCA) to the AssemblyInfo.cs file generated by Visual Studio .NET as follows.

[assembly: AllowPartiallyTrustedCallersAttribute()]

**Note   **Code that uses APTCA should be reviewed to ensure that it cannot be exploited by any partially trusted malicious code. For more information, see "APTCA" in "Chapter 8 – Code Access Security in Practice" of Improving Web Application Security: Threats and Countermeasures at https://msdn.microsoft.com/en-us/library/aa302424.aspx.

Consider Using Strong-Named Assemblies

You can increase the security of your assemblies by using strong names for them. You should consider signing all of the assemblies in your application with a strong name, and modify the security policy to trust this strong name. You can sign the assembly with a strong name key pair using the Sn.exe tool. To change the security policy manually, you can use the .NET Framework Configuration MMC snap-in or Caspol.exe, a command line tool (located at %SystemRoot%\Microsoft.NET\Framework\<version>\CasPol.exe).

Your process for signing assemblies with private keys should take into account the following:

  • Use delayed signing for development. The build process to compile code can use delayed signing, using the public portion of the strong name key pair instead of the private key. To use delayed signing, the developer can add the following attributes to the Assembly.cs file for your project.

    [assembly:AssemblyKeyFile("publickey.snk")]
    [assembly:AssemblyDelaySign(true)]
    
  • Secure the generated private keys. The following command line shows the use of the strong name tool (Sn.exe), which is provided with the .NET Framework SDK, to generate the key pair (Keypair.snk) directly to a removable storage device. (In the example, the F drive used is a USB drive.)

    sn -k f:\keypair.snk
    sn -p f:\keypair.snk f:\publickey.snk
    

    The public key (Publickey.snk) is used for delayed signing by the developers. The key pair is stored in a secure location with highly restricted access.

  • Disable verification for testing. To test an assembly that has been delay signed, you can register it on test computers by using Sn.exe. Table 5.1 lists the commonly used command-line variations.

    Table 5.1   Commonly Used Command-Line Variations

    Command Description
    sn -Vr assembly.dll
    Disable verification for a specific assembly.
    sn -Vr *,publickeytoken
    Disable verification for all assemblies with a particular public key. The asterisk (*) registers all delayed signed assemblies by a key corresponding to the provided public key token for verification skipping.
  • Sign with the private key for release. To complete the signing process, use the following command to sign with the private key.

    sn -r assembly.dll f:\keypair.snk
    

    Designated team members should then test and review the assembly, before signing it off for use in the organization.

For more information about delayed signing and the process explained in this section, see the following resources in Improving Web Application Security: Threats and Countermeasures:

See also the following resource:

  • "Strong Name Signing Using Smart Cards in Enterprise Software Production Environment" at [Content link no longer available, original URL:"http://www.dotnetthis.com/Articles/SNandSmartCards.htm"] .

Avoid Giving Full Trust to Restricted Zones

As a quick workaround to resolve the security issues with partially trusted applications, you might be tempted to give full trust to restricted zones such as the Internet or Local Intranet zone. Doing so allows any application to run without code access security checks on your local system, which becomes an issue if the application is from a malicious source. However, if deployment scenarios are considered during the design phase, you should not have to open up security to allow applications to run.

Designing Fully Trusted Applications

Because partially trusted applications may have very little access to system resources, your application may require more permissions than are assigned to it by default to operate properly. Applications that need to be able to perform tasks such as launching Office applications or Microsoft Internet Explorer, calling into legacy COM components, or writing to the file system need to run with permissions that enable these operations explicitly.

It can be tempting to assign your application as a fully trusted application so that it is assigned all possible permissions. However, it is more secure to design and deploy your application to request the minimum amount of permissions required for it to operate properly. If you do need to run your application as a fully trusted application, you should consider the following guidelines:

  • Identify the types of resources your assembly needs to access and assess the potential threats that are likely to occur if the assembly is compromised.

  • Identify the trust level of your target environment because code access security policy may constrain what your assembly is allowed to do.

  • Reduce the attack surface by using the public access modifier only for classes and members that form part of the assembly's public interface. Wherever possible, restrict access to all other classes and members using private or protected access modifier.

  • Use the sealed keyword to prevent inheritance of classes that are not designed as a base class as shown in the following code.

    public sealed class NobodyDerivesFromMe
    {...}
    
  • Where possible, use declarative class level or method level attributes to restrict access to members of the specified Windows group as shown in the following code.

    [PrincipalPermission(SecurityAction.Demand,Role=@"DomainName\WindowsGroup")]
    public sealed class Orders()
    {...}
    
  • Establish a secure build process for delayed signing, testing, security reviews, and securing the private keys.

Summary

Smart client applications are distributed in nature, so to manage security effectively for them, you need to consider security at the server, the client, and the network connection between the two. Specific smart client considerations include designing secure authentication, authorization, data validation, and securing sensitive data. You should also examine how to use code access security, to manage security at code level rather than user level.

patterns & practices Developer Center

© Microsoft Corporation. All rights reserved.