Background
I got the task of setting up CI. It was decided to use the transformation of configuration files and store confidential data in encrypted form. You can encrypt and decrypt them using the Key Container.
Key container
Every Windows OS has sets of generated keys. The key is generated either on the account or on the machine. The keys generated by the machine can be viewed along this path C: \ ProgramData \ Microsoft \ Crypto \ RSA \ MachineKeys. This is where the key that we will create next will go.
Key creation
We start cmd from the administrator and switch to the directory with aspnet_regiis, I have C: \ Windows \ Microsoft.NET \ Framework64 \ v4.0.30319
Execute the command
aspnet_regiis -pc "TestKey" -exp
exp - is added so that you can export the key in the future
TestKey - the name of our Key Container
Key Export
Team
aspnet_regiis -px "TestKey" :\TestKey.xml -pri
TestKey - name Key Container
C: \ TestKey.xml - the path where the file will be exported
pri - add a private key to export
Import key
Team
aspnet_regiis -pi "TestKey" :\TestKey.xml
TestKey - name Key Container
C: \ TestKey.xml - the path from where the file will be exported
Setting Rights
In order for your application or IIS to work with key container, you need to configure rights for it.
This is done by the team
aspnet_regiis -pa "TestKey" "NT AUTHORITY\NETWORK SERVICE"
TestKey - name Key Container
NT AUTHORITY \ NETWORK SERVICE - who will be given access to the key
By default, IIS has ApplicationPoolIdentity for the pool.
The Microsoft documentation (see link 2) describes ApplicationPoolIdentity as:
ApplicationPoolIdentity : When a new application pool is created, IIS creates a virtual account that has the name of the new application pool and that runs the application pool worker process under this account. This is also a least-privileged account.
Therefore, for IIS to be able to decrypt the config, Identity must be configured in the pool for the account, or you can select "NETWORK SERVICE" and give it rights.
Adding a section to config
<configProtectedData defaultProvider="RsaProtectedConfigurationProvider"> <providers> <add name="CustomRsaProtectedConfigurationProvider" type="System.Configuration.RsaProtectedConfigurationProvider,System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" description="Uses RsaCryptoServiceProvider to encrypt and decrypt" keyContainerName="TestKey" cspProviderName="" useMachineContainer="true" useOAEP="false"/> </providers> </configProtectedData>
Also key container and RsaProtectedConfigurationProvider are defined globally in files
C: \ Windows \ Microsoft.NET \ Framework \ v4.0.30319 \ Config \ machine.config, C: \ Windows \ Microsoft.NET \ Framework64 \ v4.0.30319 \ Config \ machine.config
<configProtectedData defaultProvider="RsaProtectedConfigurationProvider"> <providers> <add name="RsaProtectedConfigurationProvider" type="System.Configuration.RsaProtectedConfigurationProvider,System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" description="Uses RsaCryptoServiceProvider to encrypt and decrypt" keyContainerName="NetFrameworkConfigurationKey" cspProviderName="" useMachineContainer="true" useOAEP="false"/> <add name="DataProtectionConfigurationProvider" type="System.Configuration.DpapiProtectedConfigurationProvider,System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" description="Uses CryptProtectData and CryptUnProtectData Windows APIs to encrypt and decrypt" useMachineProtection="true" keyEntropy=""/> </providers> </configProtectedData>
Encryption
Encryption itself can be done in three ways.
Command Line Encryption
aspnet_regiis.exe -pef connectionStrings :\Site -prov "CustomRsaProtectedConfigurationProvider"
C: \ Site - path to the file with the config.
CustomRsaProtectedConfigurationProvider - our provider specified in the config called key container.
Encryption through a written application
private static string provider = "CustomRsaProtectedConfigurationProvider"; public static void ProtectConfiguration() { Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); ConfigurationSection connStrings = config.ConnectionStrings; if (connStrings != null && !connStrings.SectionInformation.IsProtected && !connStrings.ElementInformation.IsLocked) { connStrings.SectionInformation.ProtectSection(provider); connStrings.SectionInformation.ForceSave = true; config.Save(ConfigurationSaveMode.Full); } } public static void UnProtectConfiguration(string path) { Configuration config = ConfigurationManager.OpenExeConfiguration(path); ConfigurationSection connStrings = config.ConnectionStrings; if (connStrings != null && connStrings.SectionInformation.IsProtected && !connStrings.ElementInformation.IsLocked) { connStrings.SectionInformation.UnprotectSection(); } }
Bicycle
When there is a file transformation and you need to encrypt sections separately from the entire config, then only a self-written version is suitable. We create an instance of the RsaProtectedConfigurationProvider class, take a node from xml and encrypt it separately, then replace the original xml node with our encrypted one and save the result.
public void Protect(string filePath, string sectionName = null) { XmlDocument xmlDocument = new XmlDocument { PreserveWhitespace = true }; xmlDocument.Load(filePath); if (xmlDocument.DocumentElement == null) { throw new InvalidXmlException($"Invalid Xml document"); } sectionName = !string.IsNullOrEmpty(sectionName) ? sectionName : xmlDocument.DocumentElement.Name; var xmlElement = xmlDocument.GetElementsByTagName(sectionName)[0] as XmlElement; var config = new NameValueCollection { { "keyContainerName", _settings.KeyContainerName }, { "useMachineContainer", _settings.UseMachineContainer ? "true" : "false" } }; var rsaProvider = new RsaProtectedConfigurationProvider(); rsaProvider.Initialize(_encryptionProviderSettings.ProviderName, config); var encryptedData = rsaProvider.Encrypt(xmlElement); encryptedData = xmlDocument.ImportNode(encryptedData, true); var createdXmlElement = xmlDocument.CreateElement(sectionName); var xmlAttribute = xmlDocument.CreateAttribute("configProtectionProvider"); xmlAttribute.Value = _encryptionProviderSettings.ProviderName; createdXmlElement.Attributes.Append(xmlAttribute); createdXmlElement.AppendChild(encryptedData); if (createdXmlElement.ParentNode == null || createdXmlElement.ParentNode.NodeType == XmlNodeType.Document || xmlDocument.DocumentElement == null) { XmlDocument docNew = new XmlDocument { InnerXml = createdXmlElement.OuterXml }; docNew.Save(filePath); } else { xmlDocument.DocumentElement.ReplaceChild(createdXmlElement, xmlElement); xmlDocument.Save(filePath); } }
References
1.docs.microsoft.com/en-us/previous-versions/53tyfkaw
2.support.microsoft.com/en-za/help/4466942/understanding-identities-in-iis