none
Bug with encrypting configuration file using RSAProtectedConfigurationProvider?

    Question

  • Hi, I'm trying to encrypt my windows forms application configuration file using the example here:
    http://msdn2.microsoft.com/en-us/library/system.configuration.rsaprotectedconfigurationprovider.aspx
    It works fine for all my users except one, who gets a ConfigurationErrorsException / CryptographicException.  I've created a test console application using exactly the code from the above example (C# version) but this user gets the exception listed below.  Is this a bug in the RSAProtectedConfigurationProvider?  Or is there something wrong with this users's profile - this user gets this problem even when running the application on others' machines, but it works fine for other users on the same machines.  If it was a problem with the profile, where could I start looking?

    The exception that he gets (the app pauses for some seconds before it raises the exception) is:
    Unhandled Exception: System.Configuration.ConfigurationErrorsException: An error occurred executing the configuration section handler for connectionStrings. ---> System.Configuration.ConfigurationErrorsException: Failed to encrypt the section 'connectionStrings' using provider 'RsaProtectedConfigurationProvider'. Error message from the provider: Object already exists.
     ---> System.Security.Cryptography.CryptographicException: Object already exists.
       at System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr)
       at System.Security.Cryptography.Utils._CreateCSP(CspParameters param, Boolean randomKeyContainer, SafeProvHandle& hProv)
       at System.Security.Cryptography.Utils.CreateProvHandle(CspParameters parameters, Boolean randomKeyContainer)
       at System.Security.Cryptography.Utils.GetKeyPairHelper(CspAlgorithmType keyType, CspParameters parameters, Boolean randomKeyContainer, Int32 dwKeySize, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle)
       at System.Security.Cryptography.RSACryptoServiceProvider.GetKeyPair()
       at System.Security.Cryptography.RSACryptoServiceProvider..ctor(Int32 dwKeySize, CspParameters parameters, Boolean useDefaultKeySize)
       at System.Security.Cryptography.RSACryptoServiceProvider..ctor(CspParametersparameters)
       at System.Configuration.RsaProtectedConfigurationProvider.GetCryptoServiceProvider(Boolean exportable, Boolean keyMustExist)
       at System.Configuration.RsaProtectedConfigurationProvider.Encrypt(XmlNode node)
       at System.Configuration.ProtectedConfigurationSection.EncryptSection(String clearXml, ProtectedConfigurationProvider provider)
       at System.Configuration.Internal.InternalConfigHost.System.Configuration.Internal.IInternalConfigHost.EncryptSection(String clearTextXml, ProtectedConfigurationProvider protectionProvider, ProtectedConfigurationSection protectedConfigSection)
       at System.Configuration.Internal.DelegatingConfigHost.EncryptSection(String clearTextXml, ProtectedConfigurationProvider protectionProvider, ProtectedConfigurationSection protectedConfigSection)
       at System.Configuration.Internal.DelegatingConfigHost.EncryptSection(String clearTextXml, ProtectedConfigurationProvider protectionProvider, ProtectedConfigurationSection protectedConfigSection)
       at System.Configuration.MgmtConfigurationRecord.GetConfigDefinitionUpdates(Boolean requireUpdates, ConfigurationSaveMode saveMode, Boolean forceSaveAll, ConfigDefinitionUpdates& definitionUpdates, ArrayList& configSourceUpdates)
       --- End of inner exception stack trace ---
       at System.Configuration.MgmtConfigurationRecord.GetConfigDefinitionUpdates(Boolean requireUpdates, ConfigurationSaveMode saveMode, Boolean forceSaveAll, ConfigDefinitionUpdates& definitionUpdates, ArrayList& configSourceUpdates)
       --- End of inner exception stack trace ---
       at System.Configuration.MgmtConfigurationRecord.GetConfigDefinitionUpdates(Boolean requireUpdates, ConfigurationSaveMode saveMode, Boolean forceSaveAll, ConfigDefinitionUpdates& definitionUpdates, ArrayList& configSourceUpdates)
       at System.Configuration.MgmtConfigurationRecord.SaveAs(String filename, ConfigurationSaveMode saveMode, Boolean forceUpdateAll)
       at System.Configuration.Configuration.SaveAsImpl(String filename, ConfigurationSaveMode saveMode, Boolean forceSaveAll)
       at System.Configuration.Configuration.Save(ConfigurationSaveMode saveMode)
       at MSConsoleExample1.UsingRsaProtectedConfigurationProvider.ProtectConfiguration()
       at MSConsoleExample1.UsingRsaProtectedConfigurationProvider.Main(String[] args)

    I also made a sample app to check if other operations failed, but he is able to open and save the configuration file without encryption with no problems, and is able to encrypt and decrypt data using the RSACryptoServiceProvider with this sample code with no problems too:
    http://msdn2.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider(VS.80).aspx

    Any help would be greatly appreciated.

    regards,

    - Rory
    Thursday, April 26, 2007 2:38 PM

Answers

  • As I posted in reply to another question, this feature is really designed for server usage.

    Here's the info I posted:

     

    This is primarily useful for web servers and service servers.

    The actual encryption is either based on an RSA key or DPAPI, which means the key is stored in the local machine.

    The decryption can only be done on the machine where the app config was encrypted (unless you export the RSA key and import it into another machine).

    Even if you choose "user"-based keys instead of "machine"-based keys, the keys are still local to a machine, but now only a single user account token can decrypt the data.

     

    So, somehow, the RSA key you used has to be imported on each machine, if you used "machine"-level encryption. That means the RSA key will be saved outside of a user-specific key container. If you chose "user"-level encryption, then the RSA key has to be imported into specific user key containers. If the user doesn't have access to the key container in question, that could cause a crypto error.

     

    Regardless, I don't think this was the intended usage for the feature :-)

    Friday, April 27, 2007 2:05 AM
  • Rob,

     

    This is the location where all of the keys are being put:

    C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys

     

    I created the key as a machine key and it still cannot be accessed by users other than the one who installs it. I've given Administrators access to the key, of which these other users are part of that group. ( aspnet_regiis -pa "MyKey" "Administrators" )

     

    Now, here's another twist to the whole thing:

     

    I changed my build process to import the key, grant access to "NT AUTHORITY\NETWORK SERVICE", then encrypt the connection strings, then revoke access, and delete the key. STILL only one person can  actually perform the encryption. However, the other user can import, grant, revoke and delete. The error is always the same: Object already exists.

     

    Here are the complete list of commands I am trying:

     

    aspnet_regiis -c "MyKey" -exp

    aspnet_regiis -px "MyKey" "MyKey.xml"

    The key file is then used on the server as well as the build machine.

    aspnet_regiis -pi "MyKey" "MyKey.xml"

    aspnet_regiis -pa "MyKey" "NT AUTHORITY\NETWORK SERVICE"

    aspnet_regiis -pa "MyKey" "Administrators" -full

    aspnet_regiis -pef "connectionStrings" "web.config"

    aspnet_regiis -pr "MyKey" "NT AUTHORITY\NETWORK SERVICE"

    aspnet_regiis -pr "MyKey" "Administrators"

    aspnet_regiis -pi "MyKey"

     

    The key appears in the above directory the is deleted as expected.

     

    The MSDN docs state that the key will default as a machine key, so I am assuming this is the case.

     

    Any other ideas?

     

    -John

    Thursday, May 10, 2007 4:46 PM

All replies

  • As I posted in reply to another question, this feature is really designed for server usage.

    Here's the info I posted:

     

    This is primarily useful for web servers and service servers.

    The actual encryption is either based on an RSA key or DPAPI, which means the key is stored in the local machine.

    The decryption can only be done on the machine where the app config was encrypted (unless you export the RSA key and import it into another machine).

    Even if you choose "user"-based keys instead of "machine"-based keys, the keys are still local to a machine, but now only a single user account token can decrypt the data.

     

    So, somehow, the RSA key you used has to be imported on each machine, if you used "machine"-level encryption. That means the RSA key will be saved outside of a user-specific key container. If you chose "user"-level encryption, then the RSA key has to be imported into specific user key containers. If the user doesn't have access to the key container in question, that could cause a crypto error.

     

    Regardless, I don't think this was the intended usage for the feature :-)

    Friday, April 27, 2007 2:05 AM
  • Hi Rob, thanks for your reply.
    I'm doing both encryption and decryption on the local machine, simply to prevent the connection string from being readable in plain text. The error is happening when I initially encrypt and save the config file, so it's not even getting to the point of decrypting it, and therefore it can't be a problem with the encryption/decryption keys not being the same.
    Perhaps it is a problem with the RSA key on his machine - any thoughts on how I'd go about looking for this?  As mentioned I was able to perform encryption & decryption using the RSACryptoServiceProvider on his machine, but I suppose this doesn't automatically use the same key as the RSAProtectedConfigurationProvider ... anyone know how to make it do so?

    Alternatively, is there another method of encrypting configuration files for client-server applications?

    Any other ideas or suggestions greatly appreciated.

    thanks,

    - Rory


    Monday, April 30, 2007 10:26 AM
  • OK, I guess the next step is to identify what configuration properties you are using for the provider. You can specify CSP info and whether or not the section is handled by a user-key or a machine-key. This will be important info before we get too far into seeing what's wrong for this particular user.

    It might be an issue of trying to access (or even write) the key/container for this one particular user, but we can eliminate a lot of possibilities by getting the info I mentioned here.
    Monday, April 30, 2007 7:40 PM
  • Has there been anymore discussion on this topic? I'm currently experiencing the exact same behavior. My problem is with a user-key. I've gone so far as to remove the key and reimport it, setting up NT AUTHORITY\NETWORK SERVICE as the accessee logged in as this person.

     

    He is logging into the same server as the other users that do builds, and they are able to perform the build. Just not him.

     

    I really need to figure this out as it is stopping a build process from occurring.

     

    -John

    Tuesday, May 08, 2007 5:28 PM
  • Hi John, sorry I've made no progress on this problem.
    I intend to put some more time into it, but if anyone could provide some guidance that would be great.  I haven't spent the time working out how to follow up on Rob's suggestions so if someone wants to point me in the right direction with that it'd help. Or any other suggestions.

    - Rory
    Tuesday, May 08, 2007 10:52 PM
  • Rory,

     

    Have you created the key as a user-key or a machine-key? I may have had a user-key and didn't know it since another developer had created it. I recreated it as a machine-key, but won;t be able to test with my users until tomorrow.

     

    It turns out that only the user that imported the key could encrypt the web.config. I found this out by having all the users that were going to do the build try and either import the key or change the access. What was weird, the user that imported the key couldn't change the access, but the user that couldn't encrypt could change the access! And another user couldn't do either! The one that couldn't encrypt could reimport the key, but it would stay owned by the original importer and since this was the case, then I guess he had access rights to change access, just not encrypt. The third user would get "Object already exists" errors when trying to import the key and "Key not found" errors when trying to change access.

     

    So far, I've had the user that originally import the key manually delete it then reimport it and change the access. Tomorrow though, I'm going to have him reimport a new machine-key and see if that changes things. I'll definitely post my findings here and maybe give you some possible direction to look in.

     

    -John

    Tuesday, May 08, 2007 11:12 PM
  • I'm using exactly the code from the example here:
    http://msdn2.microsoft.com/en-us/library/system.configuration.rsaprotectedconfigurationprovider.aspx
    So I'm not explicitly creating any keys, user or machine, just using the default behaviour of the configuration section and configuration provider classes.  I'm sure it's doing similar things to your problems behind the scenes, it's just frustrating that it's not very transparent or documented so far as I can tell.

    It definitely sounds like you should be using machine-level keys though, good luck tomorrow and I look forward to any solutions you find.

    - Rory


    Tuesday, May 08, 2007 11:26 PM
  • I'm using this:

    http://msdn2.microsoft.com/en-us/library/dtkwfdky.aspx

     

    Which is also referenced on the page you are using.

     

    -John

    Tuesday, May 08, 2007 11:40 PM
  • This is why I asked whether the config protection was using user-based keys or machine-based keys. The base MS RSA provider utilizes key stores that are set on a per-user basis, (in user profiles). Any user that tries to access the keys in the store of another user won't be able to do so. There is a special store for the machine, which allows any authenticated account *on the machine* to access the key in that store. Moreover, if the user cannot log onto a machine, or their profile has not been loaded onto the machine, the user-based key store will be unavailable to them.

    Stores can typically (physically) be folders or registry keys and the keys themselves are stored in files or reg entries (this depends on the actual crypto service provider installed on the machine). In both cases, if it's a user-based store, it is tied specifically to the user profile, which must be loaded at the time (if a user is logged in, for example). In the case of a machine store (either folder or reg entry), the key file can further be protected by adding access permissions and restrictions to it, which can further prevent certain users from accessing the keys in a machine store.

    So when  you import the key, all the parameters  you use in the command are very important. You must specify a store that the users will be able to access.
    Wednesday, May 09, 2007 9:47 PM
  • Sorry Rob, excuse my ignorance in this area, but I don't understand how to translate what you're saying to my problem.  I haven't imported any keys, I haven't explicitly protected any keys, the user is executing the code while logged in to the machine and I'm not specifying the store to use.
    I'm using the code below (from here) and the only parameter it really passes is the provider to use:

    public class UsingRsaProtectedConfigurationProvider
    {

    // Protect the connectionStrings section.
    private static void ProtectConfiguration()
    {

    // Get the application configuration file.
    System.Configuration.Configuration config =
    ConfigurationManager.OpenExeConfiguration(
    ConfigurationUserLevel.None);

    // Define the Rsa provider name.
    string provider =
    "RsaProtectedConfigurationProvider";

    // Get the section to protect.
    ConfigurationSection connStrings =
    config.ConnectionStrings;

    if (connStrings != null)
    {
    if (!connStrings.SectionInformation.IsProtected)
    {
    if (!connStrings.ElementInformation.IsLocked)
    {
    // Protect the section.
    connStrings.SectionInformation.ProtectSection(provider);

    connStrings.SectionInformation.ForceSave = true;
    config.Save(ConfigurationSaveMode.Full);

    Console.WriteLine("Section {0} is now protected by {1}",
    connStrings.SectionInformation.Name,
    connStrings.SectionInformation.ProtectionProvider.Name);

    }
    else
    Console.WriteLine(
    "Can't protect, section {0} is locked",
    connStrings.SectionInformation.Name);
    }
    else
    Console.WriteLine(
    "Section {0} is already protected by {1}",
    connStrings.SectionInformation.Name,
    connStrings.SectionInformation.ProtectionProvider.Name);

    }
    else
    Console.WriteLine("Can't get the section {0}",
    connStrings.SectionInformation.Name);

    }
    ...

    My understanding is that this will use the Machine-level RSA key container called "NetFrameworkConfigurationKey".  I've just tried adding the user to the ACL for this key (on his machine) and see that there is a problem:

    C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>aspnet_regiis -pa "NetFrameworkConfigurationKey" "aioi-europe\John Smith"
    Adding ACL for access to the RSA Key container...
    The RSA key container was not found.
    Failed!
     
    C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>aspnet_regiis -px "NetFrameworkConfigurationKey" rjk_exported_keypair
    Exporting RSA Keys to file...
    The RSA key container was not found.
    Failed!
     
    C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>aspnet_regiis -pc "NetFrameworkConfigurationKey"
    Creating RSA Key container...
    The RSA key container could not be opened.
    Failed!

    On my machine I can do the following

    C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>aspnet_regiis -pa "NetFrameworkConfigurationKey" "aioi-europe\Rory"
    Adding ACL for access to the RSA Key container...
    Succeeded!

    Any idea why the key container wouldn't be there for a user and can't be created?  Is there a way to look at the key containers that are on a machine?

    thanks,

    - Rory






    Thursday, May 10, 2007 1:41 PM
  • Rob,

     

    This is the location where all of the keys are being put:

    C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys

     

    I created the key as a machine key and it still cannot be accessed by users other than the one who installs it. I've given Administrators access to the key, of which these other users are part of that group. ( aspnet_regiis -pa "MyKey" "Administrators" )

     

    Now, here's another twist to the whole thing:

     

    I changed my build process to import the key, grant access to "NT AUTHORITY\NETWORK SERVICE", then encrypt the connection strings, then revoke access, and delete the key. STILL only one person can  actually perform the encryption. However, the other user can import, grant, revoke and delete. The error is always the same: Object already exists.

     

    Here are the complete list of commands I am trying:

     

    aspnet_regiis -c "MyKey" -exp

    aspnet_regiis -px "MyKey" "MyKey.xml"

    The key file is then used on the server as well as the build machine.

    aspnet_regiis -pi "MyKey" "MyKey.xml"

    aspnet_regiis -pa "MyKey" "NT AUTHORITY\NETWORK SERVICE"

    aspnet_regiis -pa "MyKey" "Administrators" -full

    aspnet_regiis -pef "connectionStrings" "web.config"

    aspnet_regiis -pr "MyKey" "NT AUTHORITY\NETWORK SERVICE"

    aspnet_regiis -pr "MyKey" "Administrators"

    aspnet_regiis -pi "MyKey"

     

    The key appears in the above directory the is deleted as expected.

     

    The MSDN docs state that the key will default as a machine key, so I am assuming this is the case.

     

    Any other ideas?

     

    -John

    Thursday, May 10, 2007 4:46 PM
  • Ok, I think I figured out my issue.

     

    In the -pef command, I need to add the -prov modifier.

     

    Ex. aspnet_regiis -pef "MyKey" "web.config" -prov "MyProvider"

     

    Even though the provider is stated in the web.config, AND aspnet_regiis is SUPPOSED to see this (according to another MSDN doc) it still needs to be identified.

     

    So far, my problem has been solved. I'll let you know for sure in about an hour...

     

    -John

    Thursday, May 10, 2007 5:03 PM
  • Well, that solved my issue.

     

    I would assume that your issue may be similar, Rory. You might want to consider creating the key file and trying it that way to see if it works. If it does, then you can step back to using the default machine key. Good luck! I'll check back later and see if you've made any progress and throw you my other 2 cents.

     

    Thanks for the space to at least vent and solve my own problem

    -John

    Thursday, May 10, 2007 5:27 PM
  • Ah, my bad. I assumed you were specifying the provider. That always needs to be done because you can actually specify multiple providers (in fact, by default there are two at the machine config level, one for DPAPI and one for RSA if I remember correctly), and sections can be protected with different providers, so each call to Protect must have the provider element name.
    Thursday, May 10, 2007 11:58 PM
  • Rob,

     

    One of the really cool MSDN doc things though is that it doesn't include the -prov argument for the -pef command. Ok, so that's not too cool. I found it by reviewing other MSDN articles and came across it in the above mentioned examples. Thanks for your help, and I hope Rory has come across a similar answer to his issue.

     

    -John

    Wednesday, May 16, 2007 3:52 AM
  • In my web application this error "Object already exists" means that i've no permissons to open the RSAkey container. The solutions was to add an ACL with read permissons to the user that runs the asp.net process.

    Please check this link .


    Best Regards,
    Rodolfo Cardoso
    Rodolfo Cardoso
    • Proposed as answer by MTLawrence Friday, October 21, 2011 9:02 PM
    • Unproposed as answer by MTLawrence Friday, October 21, 2011 9:02 PM
    Thursday, April 23, 2009 10:05 AM
  • I had the same problem which started with an asp.net error stating "Failed to decrypt using Provider <provider name> The key container could not be opened".

    Here is how it was fixed in this case.

    1) Get the Identity of the app pool user.   Drop an aspx page in your APP and request that page.

    <%@ Page Language="C#" %>
    <%
    Response.Write(System.Security.Principal.WindowsIdentity.GetCurrent().Name);
    %>

    Mine was NT AUTHORITY\NETWORK SERVICE

    II) Now you need to make sure this user has permissions to READ from the directory used for decryption!! There are many great posts about everything BUT I FOUND NONE that point to the default location on MSDN.

    For my Win 2003 was C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA

    III) Check the folder permissions on RSA or DSS, mine was RSA and fix them (your done).  *This was what I got stuck on because simply issuing the below you don't need to know where it is. 

    aspnet_regiis -pa "MyKey" "NT AUTHORITY\NETWORK SERVICE"

    When I issued this command I got RSA key not found!  UGGGH!  I didn't work on the app I was trying to fix and had no idea why the key was LOST to me.  It turns otu even when I RAN as ADMIN I must not have had permissions to change permissions via aspnet_regiis.  So I drilled into the RSA folder and found NETWORK SERVICE was missing. 

    IV) Manually RESET permissions to allow your user to READ from RSA and DSS. 

    I recycled the app pool and the errors went away.  Thanks for all the posts I spent a couple hours on this nasty and I hope anyone who has the same issue finds this.

    ML

     

     

     

    Friday, October 21, 2011 9:19 PM
  • I am using the following way....

    • Run the Command Window in Administrator Mode. (In Windows Server 2008 , type cmd and press CTRL+SHIFT+ENTER)
    • Go to the folder C:\Windows\Microsoft.Net\Framework\v4.0.30319\ using change directory command (cd).
    • Type the following command to create RSA Key Container. aspnet_regiis -pc "NetFrameworkConfigurationKey" –exp
    • Type the following (to add ACL for access to the RSA Key Container) after replacing the highlighted dominName\UserName with proper values. aspnet_regiis -pa "NetFrameworkConfigurationKey" "ZZZ\XXXX".
    • Type the following and press enter to encrypt the connections string in Web.Config. aspnet_regiis.exe -pef "connectionStrings" "C:\XXX\XXX"

    Thanks

    Lijo

    Monday, October 31, 2011 6:34 PM