So just an intro before getting into the nitty gritty, these are my experiences personally when dealing with the RSAProtectedConfigurationProvider combination of tools in order to get sections of my web.config for an enterprise web application encrypted and some of the hassles along the way. As with anything cryptosecurity-related, there is some cursing involved.
Create an RSA Key
The first step to getting RSA encryption into your web application’s web.config, you need to create a common key that can be shared across servers and/or development environments. To do this, open up a Visual Studio command prompt (or navigate a regular command prompt over to your %WINDIR%\Microsoft.NET\Framework(64)\{insert .net version}\ directory) as an administrator and type in
aspnet_regiis -pc "ProjectKeyNameHere" -exp
This will create a key for you in the %PROGRAMDATA%\Microsoft\Crypto\RSA\MachineKeys directory
Each key is given its own unique, seemingly nonsensical filename. However, the naming convention is actually the calculated key hash based off of the keyname itself, followed by the machine key GUID, and separated by an underscore. The key hash is calculated by an interesting algorithm when it gets imported into the keystore, and the machine GUID can simply be found in the registry under HKLM:\SOFTWARE\Microsoft\Cryptography.
Quick Trivia Fact: The key “c2319c42033a5ca7f44e731bfd3fa2b5_…” is the key that is used by IIS to encrypt the MetaBase. Delete or mess with this key and the IIS server on the machine will cease to function.
So even though you can modify permissions to keys and even delete them directly from this directory, DO NOT MANUALLY EDIT PERMISSIONS OR DELETE KEYS FROM THIS DIRECTORY OR YOU WILL SCREW THINGS UP. Use the aspnet_regiis tool to manage your keys instead. Trust me on this (or take the risk and have fun troubleshooting later).
Exporting the key
Now, in order to share the key to other developers and/or machines, you will need to export the key into an XML file. This can be done by running the following command:
aspnet_regiis -px "ProjectKeyNameHere" "C:\location\for\key\KeyName.xml" -pri
This will give you a plain text XML file which describes the private key. As with anything that has “private” in its name, treat this file with respect and proper security procedures (aka, don’t throw this up on a public share, make sure you keep track of where it goes, etc).
Installing existing (XML) key to machine(s)
Once you send off the XML file in a secure fashion to another developer or machine, you can then run the following command on that machine in order to import the key into that machine’s RSA key store:
aspnet_regiis -pi "ProjectKeyNameHere" "C:\location\of\key\KeyName.xml"
Keep in mind, the “ProjectKeyNameHere” name has to be the same throughout the entire process, as this is the name of the key that will be identified in the web.config later on.
At this point, any administrative user on the box can now use the key to encrypt/decrypt information. No other (regular/non-admin) user will have access to read the key — this includes the user that IIS AppPools run under. The names or groups of the users that have this permission change based on environment, but is usually “NT AUTHORITY\NETWORK SERVICE”. In order to grant permissions for other users to read the key, run the following command:
aspnet_regiis -pa "ProjectKeyNameHere" "UsersOrGroups"
If you’re using the impersonation setting in IIS for your web application, read the “Impersonation permission issues” section below.
Adding key to web.config
In order for IIS to know which key to use for encryption/decryption, we first have to turn off the default RSAConfigurationProvider in the web.config and then add in our own key that we created. To do this, simply add the following to your web.config (directly under the root configuration node):
<configprotecteddata> <providers> <remove name="RSAProtectedConfigurationProvider"> <add keycontainername="ProjectKeyNameHere" usemachinecontainer="true" description="Uses RsaCryptoServiceProvider to encrypt and decrypt" name="RsaProtectedConfigurationProvider" type="System.Configuration.RsaProtectedConfigurationProvider, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> </add></remove></providers> </configprotecteddata>
Change the assembly version number for System.Configuration.RsaProtectedConfigurationProvider to whichever version of .NET you are using for your application (e.g. 2.0.0.0 for 2.0/3.5).
After saving the file, you will now be able to use aspnet_regiis to encrypt and decrypt sections of your web.config file as you see fit.
To encrypt (in this example, the connectionStrings node):
aspnet_regiis -pef "connectionStrings" "c:\path\to\web.config\"
To decrypt
aspnet_regiis -pdf "connectionStrings" "c:\path\to\web.config\"
Note that I supply the path to the config file, but I don’t use the filename itself in the parameter (e.g. Don’t use c:\path\to\web.config\web.config). Also note that you cannot encrypt the entire config file, only nodes under the root configuration node (it would be a bad idea to encrypt the entire file as the part to know how to decrypt the file would also be encrypted).
You will also be able to use tools such as the Enterprise Library Config Tool in order to do this for you automatically should you so desire.
Reference: ASP.NET IIS Registration Tool http://msdn.microsoft.com/en-us/library/k6h9cz8h(v=vs.80).aspx
Running publish commands to encrypt config
Personally, I find it easiest when I don’t encrypt any data in the web.config file until I’m ready to publish (so if I have to change a connectionString, I won’t have to manually decrypt, change, and then re-encrypt each time). This process will vary based on which method you use to publish your web application. I will only be covering the file system publish method, but make sure to check out the reference links below for compiling your own methods for publishing.
For MSBuild functionality, you can either store these commands in the main .csproj file if you want the action to occur globally across all the publish profiles that you have, or you can set it individually in each .publxml file (recommended).
Now, what I ended up doing for the file system publish was capturing an event after the build and web.config transform but before the files were moved to the filesystem location that was specified. Under the root Project node in the .pubxml file, I added the following:
<target name="BeforePublish" aftertargets="CopyAllFilesToSingleFolderForPackage"> <getframeworkpath> <output taskparameter="Path" propertyname="FrameworkPath"> </output></getframeworkpath> <exec command="$(FrameworkPath)\aspnet_regiis.exe -pef "connectionStrings" "$(MSBuildProjectDirectory)\$(IntermediateOutputPath)Package\PackageTmp""> </exec></target>
A few things to note here. The AfterTargets variable was a pain in the butt to find (and apparently only works well with msbuild 4.0). It is literally not documented anywhere officially (or nowhere I can find on the internet at the moment). But this will capture the moment before the files are moved to the destination file system location. The variables in the Exec Command node (such as $(MSBuildBinPath)) are all listed under the MSBuild documentation, which is what I ended up using. If you use quotes inside of the command, make sure to use “"” as it is an XML file, after all. If you have multiple sections that you want to encrypt, you will have to create an Exec Command node for each one.
Reference: How to: Extend the Visual Studio Build Process http://msdn.microsoft.com/en-us/library/ms366724.aspx
Reference: MSBuild Concepts http://msdn.microsoft.com/en-us/library/vstudio/dd637714.aspx
Reference: How to: Use Environment Variables in a Build http://msdn.microsoft.com/en-us/library/ms171459.aspx
Reference: GetFrameworkPath Task http://msdn.microsoft.com/en-us/library/ms164297.aspx
Impersonation permission issues
One of the first problems I ran across was the fact that when IIS has the impersonation flag set to true for the web application, it will use the impersonated account to access the RSA key in order to decrypt the configuration file. Unfortunately, there is no way around this if you MUST have impersonation: you will have to grant access to the key for each user who is using the application. An easy way to do this is to grant permission to the group needing access to the application. Unfortunately, if you have something like Windows Authentication turned on, that group may end up being “Domain Users” or goodness forbid “Everyone”:
aspnet_regiis -pa "ProjectKeyNameHere" "Everyone"
This sort of sucks in terms of security, I know. Right now, there really isn’t a better way of dealing with it without rewriting your application and turning off impersonation.
Cannot read key error
Check to make sure that permissions on the key are setup correctly. If it helps to debug, give permissions to everyone to see if the error goes away. If it does, find out which user/group actually needs read access to the key. If it doesn’t, here’s Google :).
Safe Handle error
“Failed to decrypt using provider ‘RsaProtectedConfigurationProvider’. Error message from the provider: Safe handle has been closed”
So this one is a pain, but again, it has everything to do with permissions. If you did everything right, and read the note above about not manually changing the permissions on the MachineKeys folder and using the aspnet_regiis tool instead, you shouldn’t have to worry about this.
On the other hand, if one of your co-workers decided that it was a good idea to change permissions on that folder without realizing the consequences it may produce, well, you have this cryptic error message to deal with.
First and foremost, check the permissions on the MachineKeys folder (%ProgramData%\Microsoft\Crypto\RSA\MachineKeys). You should see this little lock icon near the folder:
That means that general users can’t read the contents of the folder. This is a good thing. Now check your permissions for the “Everyone” group:
The !!!ONLY!!! permissions that the “Everyone” group should have checked are the “Special Permissions”. Digging a little deeper, we find out what those special permissions are:
Specifically, we have the following from the list provided by Microsoft:
- List Folder/Read Data
- Read Attributes
- Read Extended Attributes
- Create Files/Write Data
- Create Folders/Append Data
- Write Attributes
- Write Extended Attributes
- Read Permissions
and the following quote: “The default permissions on the folder may be misleading when you attempt to determine the minimum permissions that are necessary for proper installation and the accessing of certificates.”
If you grant any further permissions to the “Everyone” group, such as general read access, write access, or full control, you will see that not only will you break the functionality of the existing keys in the folder, any additional key you install to the folder will be improperly installed, as aspnet_regiis will complain about the safe handle. The former will start working once you fix the permissions on the folder (for the most part). The latter will not be fixed as they were improperly installed and will be stuck in a state of limbo. The aspnet_regiis tool will not even be able to delete those keys (it will complain about…the safe handle being closed). The only way you will be able to delete them is by manually going into the MachineKeys folder, finding out which key you have to delete (either by last modified time or by the key hash – this you can get by installing the key to another machine and comparing the first part of the filename for both). Then, once manually deleted, you can use the aspnet_regiis tool to reinstall the key.
Again, I don’t have to remind you to be very meticulous and careful when rooting around and modifying things in this folder as it can break much more than just your web application.
Reference: Default permissions for the MachineKeys folders http://support.microsoft.com/kb/278381