Building Secure ASP.NET Applications: Authentication, Authorization, and Secure Communication
Retired Content |
---|
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. |
How To: Use DPAPI (User Store) from ASP.NET 1.1 with Enterprise Services
J.D. Meier, Alex Mackman, Michael Dunner, and Srinath Vasireddy
Microsoft Corporation
Published: November 2002
Last Revised: January 2006
Applies to:
- ASP.NET 1.1
See the "patterns & practices Security Guidance for Applications Index" for links to additional security resources.
See the Landing Page for a starting point and complete overview of Building Secure ASP.NET Applications.
Summary: This How To shows you how to use DPAPI from an ASP.NET Web application or service to encrypt sensitive data. This How To uses DPAPI with the user store, which requires the use of an out of process Enterprise Services component. (14 pages)
Contents
Notes
Why Use Enterprise Services?
Why Use a Windows Service?
Summary of Steps Step 1. Create a Serviced Component That Provides Encrypt and Decrypt Methods Step 2. Use the Managed DPAPI Class Library in the Serviced Component Step 3. Create a Dummy Class That Will Launch the Serviced Component Step 4. Create a Windows Account to Run the Enterprise Services Application and Windows Service Step 5. Configure, Strong Name, and Register the Serviced Component Step 6. Create a Windows Service Application That Will Launch the Serviced Component Step 7. Install and Start the Windows Service Step 8. Write a Web Application to Test the Encryption and Decryption Routines Step 9. Modify the Web Application to Read an Encrypted Connection String from an Application Configuration File
Additional Resources
Web applications often need to store security-sensitive data, such as database connection strings and service account credentials in application configuration files. For security reasons, this type of information should never is stored in plain text and should always be encrypted prior to storage.
This How To describes how to use the Data Protection API (DPAPI) from an ASP.NET 1.1 application with Enterprise Services.
Notes
- DPAPI can work with either the machine store or user store (which requires a loaded user profile). DPAPI defaults to the user store, although you can specify that the machine store be used by passing the CRYPTPROTECT_LOCAL_MACHINE flag to the DPAPI functions.
- The user profile approach (adopted by this How To) affords an additional layer of security because it limits who can access the secret. Only the user who encrypts the data can decrypt the data. However, use of the user profile requires additional development effort when DPAPI is used from an ASP.NET Web application because you need to take explicit steps to load and unload a user profile (ASP.NET does not automatically load a user profile).
- For a related article that shows how to use DPAPI with the machine store (directly) from an ASP.NET Web application (without requiring an Enterprise Services application), see How To: Use DPAPI (Machine Store) from ASP.NET 1.1 within the Reference section of this guide.
The approach described in this How To uses a .NET serviced component running in an Enterprise Services (COM+) server application to perform the DPAPI processing for the reasons outlined in the following section, "Why Use Enterprise Services?" It also uses a Microsoft® Windows® service for the reasons in the "Why use a Windows Service?" section. The solution configuration is shown in Figure 1.
Figure 1. ASP.NET Web application uses a serviced component in an Enterprise Services server application to interact with DPAPI
In Figure1, the sequence of events is as follows:
- The Windows service control manager starts the Win32 service and automatically loads the user profile associated with the account under which the service runs. The same Windows account is used to run the Enterprise Services application.
- The Win32 service calls a launch method on the serviced component, which starts the Enterprise Services application and loads the serviced component.
- The Web application retrieves the encrypted string from the Web.config file.
- The application calls a method on the serviced component to decrypt the connection string.
- The serviced component interacts with DPAPI using P/Invoke to call the Win32 DPAPI functions.
- The decrypted string is returned to the Web application.
Why Use Enterprise Services?
DPAPI requires a Windows account password in order to derive an encryption key. The account that DPAPI uses is obtained either from the current thread token (if the thread that calls DPAPI is currently impersonating), or the process token. Furthermore, using DPAPI with the user store requires that the user profile associated with the account is loaded. This presents the following issues for an ASP.NET Web application that wants to use DPAPI with the user store:
- Calls to DPAPI from an ASP.NET application running under the default ASPNET account will fail. This is because the ASPNET account does not have a loaded user profile.
- If an ASP.NET Web application is configured to impersonate its callers, the ASP.NET application thread has an associated thread impersonation token. The logon session associated with this impersonation token is a network logon session (used on the server to represent the caller). Network logon sessions do not result in user profiles being loaded and it would also not be possible to derive an encryption key from the password because the server does not have the impersonated user's password (unless the application uses Basic authentication).
To overcome these limitations, you can use a serviced component within an Enterprise Services server application (with a fixed process identity) to provide encryption and decryption services using DPAPI.
Why Use a Windows Service?
A Windows service is used in this solution in order to ensure that a user profile is automatically loaded. When the Windows Service Control Manager (SCM) starts a service, the SCM also loads the profile of the account the service is configured to run as.
The service is then used to load the serviced component, which causes the Enterprise Services server application (in an instance of Dllhost.exe) to start.
Due to the fact that the Windows service and the serviced component are configured to both run using the same least privileged account, the serviced component has access to the loaded user profile and as a result can call DPAPI functions to encrypt and decrypt data.
If the service component is not launched from a Windows service (and the service is taken out of the picture) the user profile will not automatically be loaded. While there is a Win32 API that can be called to load a user profile (LoadUserProfile), it requires the calling code to be part of the Administrators group, which would defeat the principle of running with least privilege.
The service must be running whenever the Encrypt and Decrypt methods of the serviced component are called. When Windows services are stopped, the configured profile is automatically unloaded. At this point, the DPAPI methods within the serviced component would cease to work.
Summary of Steps
This How To includes the following steps:
- Step 1. Create a Serviced Component That Provides Encrypt and Decrypt Methods
- Step 2. Use the Managed DPAPI Class Library in the Serviced Component
- Step 3. Create a Dummy Class That Will Launch the Serviced Component
- Step 4. Create a Windows Account to Run the Enterprise Services Application and Windows Service
- Step 5. Configure, Strong Name, and Register the Serviced Component
- Step 6. Create a Windows Service Application That Will Launch the Serviced Component
- Step 7. Install and Start the Windows Service
- Step 8. Write a Web Application to Test the Encryption and Decryption Routines
- Step 9. Modify the Web Application to Read an Encrypted Connection String from an Application Configuration File
Step 1. Create a Serviced Component That Provides Encrypt and Decrypt Methods
This procedure creates a serviced component that exposes Encrypt and Decrypt methods. In a later procedure, these will be called by an ASP.NET Web application when it requires encryption services.
To create a serviced component that provides Encrypt and Decrypt methods
Start Visual Studio .NET and create a new C# class library project called DPAPIComp.
Use Solution Explorer to rename Class1.cs as DataProtectorComp.cs.
Within DataProtectorComp.cs, rename Class1 as DataProtectorComp and rename the default constructor accordingly.
Add an assembly reference to the System.EnterpriseServices.dll assembly.
Add the following using statements to the top of DataProtectorComp.cs.
using System.EnterpriseServices; using System.Security.Principal; using System.Runtime.InteropServices;
Derive the DataProtectorComp class from the ServicedComponent class.
public class DataProtectorComp : ServicedComponent
Add the following two empty public methods to the DataProtectorComp class.
public byte[] Encrypt(byte[] plainText) {} public byte[] Decrypt(byte[] cipherText) {}
Step 2. Use the Managed DPAPI Class Library in the Serviced Component
This procedure calls the managed DPAPI class library to encrypt and decrypt data. This class library encapsulates the calls to the Win32 DPAPI functions. If you have not yet created this class library, refer to "How To: Create a DPAPI Library in .NET 1.1" in the Reference section of this guide.
To call the managed DPAPI class library in the serviced component
Add a file reference to the DataProtection.dll assembly.
Add the following using statement beneath the existing using statements in DataProtectorComp.cs.
using DataProtection;
Add the following code to the Encrypt method to encrypt the supplied data.
DataProtector dp = new DataProtector( DataProtector.Store.USE_USER_STORE ); byte[] cipherText = null; try { cipherText = dp.Encrypt(plainText, null); } catch(Exception ex) { throw new Exception("Exception encrypting. " + ex.Message); } return cipherText;
Add the following code to the Decrypt method to decrypt the supplied cipher text.
DataProtector dp = new DataProtector( DataProtector.Store.USE_USER_STORE ); byte[] plainText = null; try { plainText = dp.Decrypt(cipherText,null); } catch(Exception ex) { throw new Exception("Exception decrypting. " + ex.Message); } return plainText;
Step 3. Create a Dummy Class That Will Launch the Serviced Component
This procedure creates a dummy class that exposes a single Launch method. This will be called from the Windows service to start the Enterprise Services application that hosts the serviced component.
To create a dummy class that will launch the serviced component
Add a new C# class to the project and name it Launcher.cs.
Add the following method to the class. This method will be called by the service when the service starts.
public bool Launch() { return true; }
On the Build menu, click BuildSolution.
Step 4. Create a Windows Account to Run the Enterprise Services Application and Windows Service
This procedure creates a Windows account that you will use to run the Enterprise Services application that hosts the DataProtectorComp serviced component and the Windows service. It also results in the creation of a user profile for the new account. This is required by DPAPI when it uses the user store.
To create a Windows account to run the Enterprise Services application and Windows service
- Create a new local user account called DPAPIAccount. Enter a password, clear the User must change password at next logon check box, and then select the Password never expires check box.
- Use the Local Security Policy tool in the AdministrativeTools programs group to give the account the Log on locally and Log on as a batchjob privileges.
To create a user profile for the new account
Log off Windows.
Log back on using the new DPAPIAccount.
This results in the creation of a user profile for this account.
Log off Windows and log back on as your normal developer account.
Step 5. Configure, Strong Name, and Register the Serviced Component
This procedure signs the serviced component assembly to give it a strong name. This is a mandatory requirement for assemblies containing serviced components. You will then add assembly level attributes to the serviced component assembly used to configure the serviced component within the COM+ catalog. After that, you will use the Regsvcs.exe utility to register the serviced component and create a host COM+ server application. Finally, you will set the COM+ application's "run as" identity to the service account created in the previous procedure.
To configure, strong name, and register the serviced component
Open a command window and change directory to the DPAPIComp project folder.
Use the sn.exe utility to generate a key pair used to sign the assembly.
sn -k dpapicomp.snk
Return to Visual Studio .NET and open Assemblyinfo.cs.
Locate the AssemblyKeyFile attribute and add a path to the key file within the project folder.
[assembly: AssemblyKeyFile(@"..\..\dpapicomp.snk")]
Add the following using statement to the top of the file.
using System.EnterpriseServices;
Add the following assembly level attributes to configure the COM+ application as a server application, and to specify the application's name.
[assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationName("DPAPI Helper Application")]
On the Build menu, click Build Solution to build the serviced component project.
Open a command window and go to the project output directory that contains the DPAPIComp.dll file.
Use regsvcs.exe to register the serviced component and create the COM+ application.
regsvcs DPAPIComp.dll
Start the Component Services Microsoft Management Console (MMC) snap-in.
Expand the Component Services, Computers, MyComputer, and COM+ Applications folders.
Locate and right-click DPAPI Helper Application, and then click Properties.
Click the Activation tab and confirm that the application type is set to Serverapplication.
Click the Identity tab, and then click the This user radio button.
Enter DPAPIAccount as the user, enter the appropriate password, and then click OK to close the Properties dialog box.
Step 6. Create a Windows Service Application That Will Launch the Serviced Component
This procedure creates a simple Windows service application that will launch the serviced component when it starts. This ensures that the profile of the configured account is loaded and that the serviced component is available to encrypt and decrypt data.
To create a Windows service application that will launch the serviced component
Start a new instance of Visual Studio .NET and create a new C# Windows service project called DPAPIService.
Use Solution Explorer to rename Service1.cs as DPAPIService.cs.
Within DPAPIService.cs, rename Service1 as DPAPIService and rename the default constructor accordingly.
Within DPAPIService.cs, locate the InitializedComponent method and change the service name to DPAPIService.
Set a reference to the System.EnterpriseServices.dll and System.Configuration.Install.dll assemblies.
Set a file reference to the DPAPIComp assembly.
Add the following using statement to the top of DPAPIService.cs beneath the existing using statements.
using DPAPIComp;
Locate the Main method and replace the following code
ServicesToRun = new System.ServiceProcess.ServiceBase[]{new Service1()};
with the following line.
ServicesToRun = new System.ServiceProcess.ServiceBase[]{new DPAPIService()};
Locate the OnStart method and add the following code, which will launch the DPAPIComp component whenever the service starts.
Launcher launchComponent = new Launcher(); launchComponent.Launch();
Add a new C# class file to the project and name it DPAPIServiceInstaller.
Add the following using statements to the top of DPAPIServiceInstaller beneath the existing using statement.
using System.ComponentModel; using System.ServiceProcess; using System.Configuration.Install;
Derive the DPAPIServiceInstaller class from the Installer class.
public class DPAPIServiceInstaller : Installer
Add the RunInstaller attribute at the class level as follows.
[RunInstaller(true)] public class DPAPIServiceInstaller : Installer
Add the following two private member variables to the DPAPIServiceInstaller class. The objects will be used when installing the service.
private ServiceInstaller dpApiInstaller; private ServiceProcessInstaller dpApiProcessInstaller;
Add the following code to the constructor of the DPAPIServiceInstaller class.
dpApiInstaller = new ServiceInstaller(); dpApiInstaller.StartType = System.ServiceProcess.ServiceStartMode.Manual; dpApiInstaller.ServiceName = "DPAPIService"; dpApiInstaller.DisplayName = "DPAPI Service"; Installers.Add (dpApiInstaller); dpApiProcessInstaller = new ServiceProcessInstaller(); dpApiProcessInstaller.Account = ServiceAccount.User; Installers.Add (dpApiProcessInstaller);
On the Build menu, click Build Solution.
Step 7. Install and Start the Windows Service Application
This procedure installs the Windows service using the installutil.exe utility and then starts the service.
To install and start the Windows service application
Open a command window and change directory to the Bin\Debug directory beneath the DPAPIService project folder.
Run the installutil.exe utility to install the service.
Installutil.exe DPAPIService.exe
In the SetServiceLogin dialog box, enter the user name and password of the account created earlier in Procedure 4, "Create a Windows Account to Run the Enterprise Services Application and Windows Service," and then click OK.
The user name must be of the form "authority\username."
View the output from the installutil.exe utility and confirm that the service is installed correctly.
Start the Services MMC snap-in from the AdministrativeTools program group**.**
Start the DPAPI service.
Step 8. Write a Web Application to Test the Encryption and Decryption Routines
This procedure develops a simple Web application that you will use to test the encryption and decryption routines. Later, you will also use it to decrypt encrypted data maintained within the Web.config file.
To write a Web application to test the encryption and decryption routines
Add a new C# Web application project called DPAPIWeb to the existing DPAPIComp solution.
Add an assembly reference to System.EnterpriseServices and add a project reference to the DPAPIComp project.
Open WebForm1.aspx in Design mode and create a form similar to the one shown in Figure 2. Use the IDs listed in Table 1 for the individual controls.
Table 1: WebForm1.aspx control IDs
Control ID Data To Encrypt Text Box txtDataToEncrypt Encrypted Data txtEncryptedData Decrypted Data txtDecryptedData Encrypt Button btnEncrypt Decrypt Button btnDecrypt Error Label lblError Figure 2. DPAPIWeb Web Form
Double-click the Encrypt button to display the button click event handler.
Add the following using statements to the top of the file beneath the existing using statements.
using System.Text; using DPAPIComp;
Return to the Encrypt button click event handler and add the following code to call the DataProtectorComp serviced component to encrypt the data entered via the Web form.
DataProtectorComp dp = new DataProtectorComp(); try { byte[] dataToEncrypt = Encoding.ASCII.GetBytes(txtDataToEncrypt.Text); txtEncryptedData.Text = Convert.ToBase64String( dp.Encrypt(dataToEncrypt)); } catch(Exception ex) { lblError.ForeColor = Color.Red; lblError.Text = "Exception.<br>" + ex.Message; return; } lblError.Text = "";
Display the Web form again and double-click the Decrypt button to create a button click event handler.
Add the following code to call the DataProtectorComp services component to decrypt the previous encrypted data contained within the txtEncryptedData field.
DataProtectorComp dp = new DataProtectorComp(); try { byte[] dataToDecrypt = Convert.FromBase64String(txtEncryptedData.Text); txtDecryptedData.Text = Encoding.ASCII.GetString( dp.Decrypt(dataToDecrypt)); } catch(Exception ex) { lblError.ForeColor = Color.Red; lblError.Text = "Exception.<br>" + ex.Message; return; } lblError.Text = "";
On the Build menu, click BuildSolution.
Right-click WebForm1.aspx, and then click View in Browser.
Enter a text string into the Data to Encrypt field.
Click the Encrypt button. This results in a call to the DataProtector serviced component within the COM+ application. The encrypted data should be displayed in the Encrypted Data field.
Click the Decrypt button and confirm that the original text string is displayed in the DecryptedData field.
Close the browser window.
Note If an access denied error message appears that indicates that the component's ProgID cannot be read from HKEY_CLASSES_ROOT, you probably need to re-run Regsvcs.exe to reregister the serviced component.
This error message appears if you have recompiled the serviced component assembly but not reregistered the assembly. Because the assembly version changes on each build (due to the default "1.0.*" assembly version attribute), a new CLSID is generated on each successive build. The error is due to the fact that ASP.NET cannot access this CLSID in the registry because it does not yet exist. Rerun Regsvcs.exe and restart the Web application to resolve the problem. One way to avoid this problem is by specifying the exact version number in place of the default version number.
Step 9. Modify the Web Application to Read an Encrypted Connection String from an Application Configuration File
This procedure takes an encrypted database connection string and places the encrypted cipher text into the application's Web.config file within an <appSettings> element. You will then add code to read and decrypt this string from the configuration file.
To modify the Web application to read an encrypted connection string from an application configuration file
Return to Visual Studio .NET and display the WebForm1.aspx in Designer mode.
Add another button to the form. Set its Text property to Decrypt string from config file and its ID property to btnDecryptConfig,
Double-click the button to create a button click event handler.
Add the following using statement to the top of the file beneath the existing using statements.
using System.Configuration;
Return to the btnDecryptConfig_Click event handler and add the following code to retrieve a database connection string from the <appSettings> section of the Web.config file.
DataProtectorComp dec = new DataProtectorComp(); try { string appSettingValue = ConfigurationSettings.AppSettings["connectionString"]; byte[] dataToDecrypt = Convert.FromBase64String(appSettingValue); string connStr = Encoding.ASCII.GetString( dec.Decrypt(dataToDecrypt)); txtDecryptedData.Text = connStr; } catch(Exception ex) { lblError.ForeColor = Color.Red; lblError.Text = "Exception.<br>" + ex.Message; return; } lblError.Text = "";
On the Build menu, click BuildSolution to rebuild the projects.
Right-click WebForm1.aspx, and then click View in Browser.
In the Data to Encrypt field, enter a database connection string such as the one that follows.
server=(local);Integrated Security=SSPI; database=Northwind
Click the Encrypt button.
Select the encrypted cipher text and copy it to the clipboard.
Switch to Visual Studio .NET, open Web.config and add the following <appSettings> element outside of the <system.web> element. Assign the encrypted connection string currently on the clipboard to the value attribute.
<appSettings> <add key="connectionString" value="encrypted connection string" /> </appSettings>
Save Web.config.
Click the Decrypt string from config file button and confirm that the encrypted database connection string is successfully read from the Web.config file and that the decrypted connection string is successfully displayed in the Decrypted data field.
Additional Resources
- Windows Data Protection
- How To: Create a DPAPI Library in the Reference section of this guide.
- How To: Use DPAPI (Machine Store) from ASP.NET in the Reference section of this guide.
Retired Content |
---|
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. |