3

In the following example I protect the "DemoWinApp.Properties.Settings" section of the "Sleutels.config" file.

    private static void toggleProtectionSleutelsConfig()
    {
        var fileMap = new ConfigurationFileMap(@"D:\Experimenten\ReadProtectedConfigFile\Sleutels.config");
        var configuration = ConfigurationManager.OpenMappedMachineConfiguration(fileMap);
        var sectionGroup = configuration.GetSectionGroup("applicationSettings"); // This is the section group name, change to your needs
        var section = (ClientSettingsSection)sectionGroup.Sections.Get("DemoWinApp.Properties.Settings"); // This is the section name, change to your needs
        var setting = section.Settings.Get("SecretMessage"); // This is the setting name, change to your needs
        Console.WriteLine(setting.Value.ValueXml.InnerText);

        // Toggle beveiliging
        if (!section.SectionInformation.IsProtected)
        {
            //Protecting the specified section with the specified provider
            section.SectionInformation.ProtectSection("RSA");
        }
        else
        {
            section.SectionInformation.UnprotectSection();
        }
        section.SectionInformation.ForceSave = true;
        configuration.Save(ConfigurationSaveMode.Modified);


        Console.ReadKey();
    }

The contents of the "Sleutels.config" file is:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <configSections>
  <sectionGroup name="applicationSettings"

     type="System.Configuration.ApplicationSettingsGroup, &#xD;&#xA;                    System, Version=2.0.0.0, Culture=neutral, &#xD;&#xA;                    PublicKeyToken=b77a5c561934e089">
   <section name="DemoWinApp.Properties.Settings" type="System.Configuration.ClientSettingsSection, 
                      System, Version=2.0.0.0, Culture=neutral, 
                      PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </sectionGroup>
 </configSections>
 <applicationSettings>
  <DemoWinApp.Properties.Settings>
   <setting name="SecretMessage" serializeAs="String">
    <value>This is the secret message.</value>
   </setting>
  </DemoWinApp.Properties.Settings>
 </applicationSettings>
 <configProtectedData>
  <providers>
  <add name="RSA"
       type="System.Configuration.RsaProtectedConfigurationProvider, System.Configuration, Version=2.0.0.0,&#xD;&#xA;                    Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a,&#xD;&#xA;                    processorArchitecture=MSIL"
       keyContainerName="RobinsKeys"
       useMachineContainer="true" />
  </providers>
 </configProtectedData>
</configuration>

After running the code the "Sleutels.config" file is encrypted and a RSA key container is created in C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys

If I try to export the RSA key container with the commandline:

c:\windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis -pc "RobinsKeys" –exp

Then I get the error message:

Exporting RSA Keys to file...
Key not valid for use in specified state.

This means that the RSA Key container is not marked as "exportable". If you would create an key container with the command line, then there is an optional parameter "-exp" to mark the key as exportable.

For example: aspnet_regiis -pc "RobinsKeys" -exp

Is this -exp option also available while using the section.SectionInformation.ProtectSection("RSA"); method in code or as an configuration option in the RSA provider section in the "Sleutels.config" configuration file?

Any help is appreciated!

Robin
  • 151
  • 6
  • This seem to be a similar question: https://stackoverflow.com/questions/36150555/rsa-encryption-using-aspnet-regiis If not, could you have a look and work out the differences, please? – Matt Mar 19 '20 at 13:55
  • @Matt I think they're different. This question is "If I programatically encrypt a config section and a key doesn't exist then it generates one for me. This auto-generated key is not exportable. How can I change this so the key does get generated as exportable?" – Rup Mar 19 '20 at 14:33
  • @Rup - If you programmatically encrypt then it is in your (the developer's) hands to implement key export/import. If the encryption is managed by IIS (which it is, by using aspnet_regiis), then the key being used is the machine key and can be exported [this way](https://forums.iis.net/t/1189746.aspx?Export+Import+Machine+Key). – Matt Mar 19 '20 at 14:58
  • @Matt OK, 'programmatically encrypt using the IIS APIs' - this is IIS-managed encryption, the same as aspnet_regiis, but triggered from C#. The [second reply in the thread you linked](https://forums.iis.net/post/2022858.aspx) is the problem: it's only exportable if you import the key with -exp or generate it with -exp. OP wants to know how to effectively pass the '-exp' flag into the APIs so that if it generates a key it does it -exp. – Rup Mar 19 '20 at 15:03
  • @Rup - Well, maybe [this powershell script](http://jeffgraves.me/2012/06/05/read-write-net-machine-key-with-powershell/) can be used as a starting point for developing it in C#. It provides the methods GenKey, SetKey, GetKey. Replace the used crypto provider in the coce by RSACryptoServiceProvider. – Matt Mar 19 '20 at 15:08
  • @Matt, the question in the link of your first comment is different. The question there is "I want to ask which key is used to encrypt my connection string,...". In my question I do know what the key and keycontainer is, but I want it to be exportable when the configuration object creates it for me as it is when using the command line aspnet_regiis and the -exp parameter. – Robin Mar 19 '20 at 16:23
  • @Rup, you're right my question is ""If I programatically encrypt a config section and a key doesn't exist then it generates one for me. This auto-generated key is not exportable. How can I change this so the key does get generated as exportable?". I would supplement this with the condition that I want to use the "System.Configuration.RsaProtectedConfigurationProvider" instead of building something my own with for example [rsacryptoserviceprovider](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsacryptoserviceprovider.exportparameters?view=netframework-4.8). – Robin Mar 19 '20 at 16:32
  • @Matt and @Rup, FYI, I have found the reference source code of the [RSAProtectedConfigurationProvider](https://referencesource.microsoft.com/#System.Configuration/System/Configuration/RSAProtectedConfigurationProvider.cs,1afe03f751f3ee75,references). I cannot fully digest this code, but at first glance there is no configuration attribute to mark it as exportable in the `Initialize` method. – Robin Mar 19 '20 at 17:03
  • 1
    @Robin - if you know the keys (RSA: public+private key pair) then you have everything you need. Make sure that you're generating the key pair by yourself using the RSA crypto provider, and with that replace the machine key on the server so it is using it. In that case, you don't require an export, just an import on the machine (IIS server). [Here's](https://stackoverflow.com/a/55689151/1016343) a C# code sample for the RSA crypto provider, let me know if that helps you. – Matt Mar 20 '20 at 07:42
  • @Robin - I also found [this link on ServerFault](https://serverfault.com/q/896194/165598) regarding key management on IIS. Hope that helps together with the previous link I sent you. – Matt Mar 20 '20 at 08:33
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/209989/discussion-between-robin-and-matt). – Robin Mar 20 '20 at 10:14

2 Answers2

2

To summarize the discussion, you need to create a RSA crypto container  programmatically  before you can use it to store RSA keys.

The reason is that the RSAProtectedConfigurationProvider doesn't have an option to make the automatically created RSA key container  exportable.

As you wrote in the chat, this workaround can be achieved by the following example code (I've added some output to the console, RSA parameters printed are explained here):

void Main()
{
   // Create the CspParameters object and set the key container
   // name used to store the RSA key pair.
   var cp = new System.Security.Cryptography.CspParameters();
   cp.Flags = System.Security.Cryptography.CspProviderFlags.UseMachineKeyStore;
   cp.KeyContainerName = "RobinsKeys";


   // Create a new instance of RSACryptoServiceProvider that accesses
   // the key container MyKeyContainerName.
   // If it is not already there, it will create a new one, which is exportable.
   var myRSA = new System.Security.Cryptography.RSACryptoServiceProvider(cp);
    
   // print it on console
   Console.WriteLine($"=== Container: {cp.KeyContainerName} ===");
   Console.WriteLine(myRSA.ToXmlString(true).Replace("><", ">\n<")); 
}

which can be read in more detail here. The link provided also shows how to

  • generate and save a key pair
  • get a key from a container
  • delete a key from a container

It might be useful to get a list of containers, this is discussed in a separate question.

Some more information about key containers you can find in this Microsoft article, also about available aspnet_regiis parameters.

Once a RSA container is created, it can be used by IIS. It is important to understand the difference between user-level and machine-level key containers, which is described in this documentation.


Kindly let me know if anything is missing from the discussion, and I will update this answer.

Matt
  • 25,467
  • 18
  • 120
  • 187
0

if you encrypt and save your config file programatically , the Generated Key in C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys is not Exportable.

when you try Export Key With

c:\windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis -pc "NameOfMyKey" –exp

this error is fired because the default generated key file not exportable (without Private Key) :

Key not valid for use in specified state.

for this, you must Create the CspParameters object and set the key container before encrypt and Save like this :

dont forget add this on your configfile

<configProtectedData>
    <providers>
      <add keyContainerName="MyKeyName"
               useMachineContainer="true"
               name="MyKeyName"
               type="System.Configuration.RsaProtectedConfigurationProvider,
                 System.Configuration,
                 Version=2.0.0.0,
                 Culture=neutral,
                 PublicKeyToken=b03f5f7f11d50a3a"/>
    </providers>
  </configProtectedData>

and This is Function for Encrypt or Decrypt ALL ConfigFile with specific keyname, it generate several catched exception because some sections are not cryptable.

 public static Configuration EncryptorDecryptConfigFile(string ConfigFile, string MyKeyName, bool Encrypt = true)
        {
            try
            {
                Configuration config = OpenConfiguration(ConfigFile);
                CspParameters param = new System.Security.Cryptography.CspParameters();
                param.Flags = System.Security.Cryptography.CspProviderFlags.UseMachineKeyStore;
                param.KeyContainerName = MyKeyName;
                foreach (ConfigurationSectionGroup configSectiongroup in config.SectionGroups)
                {
                    try
                    {
                        foreach (ConfigurationSection configSection in configSectiongroup.Sections)
                        {
                            try
                            {
                                if (configSection != null)
                                {
                                    if (Encrypt)
                                        if (!configSection.SectionInformation.IsProtected)
                                            configSection.SectionInformation.ProtectSection(MyKeyName);
                                        else
                                        if (configSection.SectionInformation.IsProtected)
                                            configSection.SectionInformation.UnprotectSection();
                                }
                            }
                            catch (Exception)
                            {
                            }
                        }
                    }
                    catch (Exception bb)
                    {
                    }
                }
                foreach (string key in config.Sections.Keys)
                {
                    try
                    {
                        ConfigurationSection configSection = config.Sections[key];
                        if (configSection != null)
                        {
                            if (Encrypt)
                                if (!configSection.SectionInformation.IsProtected)
                                    configSection.SectionInformation.ProtectSection(MyKeyName);
                                else
                                if (configSection.SectionInformation.IsProtected)
                                    configSection.SectionInformation.UnprotectSection();
                        }
                    }
                    catch (Exception)
                    {
                    }
                }
                return config;
            }
            catch (Exception ex)
            {
                string errorType = ex.GetType().FullName;
                return null;
            }
        }