File: System\Configuration\RSAProtectedConfigurationProvider.cs
Project: ndp\fx\src\Configuration\System.Configuration.csproj (System.Configuration)
//------------------------------------------------------------------------------
// <copyright file="RsaProtectedConfigurationProvider.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Configuration
{
    using System.Collections.Specialized;
    using System.Runtime.Serialization;
    using System.Configuration.Provider;
    using System.Xml;
    using System.Security;
    using System.Security.Cryptography;
    using System.Security.Cryptography.Xml;
    using System.IO;
    using System.Runtime.InteropServices;
    using Microsoft.Win32;
    using System.Security.Permissions;
    using System.Diagnostics.CodeAnalysis;
 
    [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
    public sealed class RsaProtectedConfigurationProvider : ProtectedConfigurationProvider
    {
        // Note: this name has to match the name used in RegiisUtility 
        const string DefaultRsaKeyContainerName = "NetFrameworkConfigurationKey";
 
        public override XmlNode Decrypt(XmlNode encryptedNode)
        {
            XmlDocument                 xmlDocument = new XmlDocument();
            EncryptedXml                exml        = null;
            RSACryptoServiceProvider    rsa         = GetCryptoServiceProvider(false, true);
 
            xmlDocument.PreserveWhitespace = true;
            ProtectedConfigurationProvider.LoadXml(xmlDocument, encryptedNode.OuterXml);
            exml = new FipsAwareEncryptedXml(xmlDocument);
            exml.AddKeyNameMapping(_KeyName, rsa);
            exml.DecryptDocument();
            rsa.Clear();
            return xmlDocument.DocumentElement;
        }
 
        public override XmlNode Encrypt(XmlNode node)
        {
            XmlDocument         xmlDocument;
            EncryptedXml        exml;
            byte[]              rgbOutput;
            EncryptedData       ed;
            KeyInfoName         kin;
            EncryptedKey        ek;
            KeyInfoEncryptedKey kek;
            XmlElement          inputElement;
            RSACryptoServiceProvider rsa = GetCryptoServiceProvider(false, false);
 
            // Encrypt the node with the new key
            xmlDocument = new XmlDocument();
            xmlDocument.PreserveWhitespace = true;
            ProtectedConfigurationProvider.LoadXml(xmlDocument, "<foo>" + node.OuterXml + "</foo>");
            exml = new EncryptedXml(xmlDocument);
            inputElement = xmlDocument.DocumentElement;
 
            using (SymmetricAlgorithm symAlg = GetSymAlgorithmProvider()) {
                rgbOutput = exml.EncryptData(inputElement, symAlg, true);
                ed = new EncryptedData();
                ed.Type = EncryptedXml.XmlEncElementUrl;
                ed.EncryptionMethod = GetSymEncryptionMethod();
                ed.KeyInfo = new KeyInfo();
 
                ek = new EncryptedKey();
                ek.EncryptionMethod = new EncryptionMethod(UseOAEP ? EncryptedXml.XmlEncRSAOAEPUrl : EncryptedXml.XmlEncRSA15Url);
                ek.KeyInfo = new KeyInfo();
                ek.CipherData = new CipherData();
                ek.CipherData.CipherValue = EncryptedXml.EncryptKey(symAlg.Key, rsa, UseOAEP);
            }
 
            kin = new KeyInfoName();
            kin.Value = _KeyName;
            ek.KeyInfo.AddClause(kin);
            kek = new KeyInfoEncryptedKey(ek);
            ed.KeyInfo.AddClause(kek);
            ed.CipherData = new CipherData();
            ed.CipherData.CipherValue = rgbOutput;
            EncryptedXml.ReplaceElement(inputElement, ed, true);
 
            rsa.Clear();
 
                // Get node from the document
            foreach (XmlNode node2 in xmlDocument.ChildNodes)
                if (node2.NodeType == XmlNodeType.Element)
                    foreach (XmlNode node3 in node2.ChildNodes) // node2 is the "foo" node
                        if (node3.NodeType == XmlNodeType.Element)
                            return node3; // node3 is the "EncryptedData" node
                return null;
 
        }
 
        public void AddKey(int keySize, bool exportable)
        {
            RSACryptoServiceProvider rsa = GetCryptoServiceProvider(exportable, false);
            rsa.KeySize = keySize;
            rsa.PersistKeyInCsp = true;
            rsa.Clear();
        }
 
        public void DeleteKey()
        {
            RSACryptoServiceProvider rsa = GetCryptoServiceProvider(false, true);
            rsa.PersistKeyInCsp = false;
            rsa.Clear();
        }
        public void ImportKey(string xmlFileName, bool exportable)
        {
            RSACryptoServiceProvider rsa = GetCryptoServiceProvider(exportable, false);
            rsa.FromXmlString(File.ReadAllText(xmlFileName));
            rsa.PersistKeyInCsp = true;
            rsa.Clear();
        }
 
        public void ExportKey(string xmlFileName, bool includePrivateParameters)
        {
            RSACryptoServiceProvider rsa = GetCryptoServiceProvider(false, false);
            string xmlString = rsa.ToXmlString(includePrivateParameters);
            File.WriteAllText(xmlFileName, xmlString);
            rsa.Clear();
        }
 
        public string   KeyContainerName    { get { return _KeyContainerName; } }
        public string   CspProviderName     { get { return _CspProviderName; } }
        public bool     UseMachineContainer { get { return _UseMachineContainer; } }
        public bool UseOAEP { get { return _UseOAEP; } }
        public bool UseFIPS { get { return _UseFIPS; } }
 
        public override void Initialize(string name, NameValueCollection configurationValues)
        {
            base.Initialize(name, configurationValues);
 
            _KeyName = "Rsa Key";
            _KeyContainerName = configurationValues["keyContainerName"];
            configurationValues.Remove("keyContainerName");
            if (_KeyContainerName == null || _KeyContainerName.Length < 1)
                _KeyContainerName = DefaultRsaKeyContainerName;
 
            _CspProviderName = configurationValues["cspProviderName"];
            configurationValues.Remove("cspProviderName");
            _UseMachineContainer = GetBooleanValue(configurationValues, "useMachineContainer", true);
            _UseOAEP = GetBooleanValue(configurationValues, "useOAEP", true);
            _UseFIPS = GetBooleanValue(configurationValues, "useFIPS", true);
            if (configurationValues.Count > 0)
                throw new ConfigurationErrorsException(SR.GetString(SR.Unrecognized_initialization_value, configurationValues.GetKey(0)));
        }
 
 
        private string _KeyName;
        private string _KeyContainerName;
        private string _CspProviderName;
        private bool _UseMachineContainer;
        private bool _UseOAEP;
        private bool _UseFIPS;
 
        public RSAParameters RsaPublicKey { get { return GetCryptoServiceProvider(false, false).ExportParameters(false); } }
 
        private RSACryptoServiceProvider GetCryptoServiceProvider(bool exportable, bool keyMustExist)
        {
            try {
                CspParameters csp = new CspParameters();
                csp.KeyContainerName = KeyContainerName;
                csp.KeyNumber = 1;
                csp.ProviderType = 1; // Dev10 Bug #548719: Explicitly require "RSA Full (Signature and Key Exchange)"
 
                if (CspProviderName != null && CspProviderName.Length > 0)
                    csp.ProviderName = CspProviderName;
 
                if (UseMachineContainer)
                    csp.Flags |= CspProviderFlags.UseMachineKeyStore;
                if (!exportable && !keyMustExist)
                    csp.Flags |= CspProviderFlags.UseNonExportableKey;
                if (keyMustExist)
                    csp.Flags |= CspProviderFlags.UseExistingKey;
 
                return new RSACryptoServiceProvider(2048, csp);
 
            } catch {
                ThrowBetterException(keyMustExist);
 
                // If a better exception can't be found, this will propagate
                // the original one
                throw;
            }
        }
 
        private void ThrowBetterException(bool keyMustExist)
        {
            SafeCryptContextHandle hProv = null;
            int success = 0;
            try {
                success = UnsafeNativeMethods.CryptAcquireContext(out hProv, KeyContainerName, CspProviderName, PROV_Rsa_FULL, UseMachineContainer ? CRYPT_MACHINE_KEYSET : 0);
                if (success != 0)
                    return; // propagate original exception
 
                int hr = Marshal.GetHRForLastWin32Error();
                if (hr == HResults.NteBadKeySet && !keyMustExist) {
                    return; // propagate original exception
                }
 
                switch (hr) {
                    case HResults.NteBadKeySet:
                    case HResults.Win32AccessDenied:
                    case HResults.Win32InvalidHandle:
                        throw new ConfigurationErrorsException(SR.GetString(SR.Key_container_doesnt_exist_or_access_denied));
 
                    default:
                        Marshal.ThrowExceptionForHR(hr);
                        break;
                }
            } finally {
                if (!(hProv == null || hProv.IsInvalid))
                    hProv.Dispose();
            }
        }
        const uint PROV_Rsa_FULL = 1;
        const uint CRYPT_MACHINE_KEYSET = 0x00000020;
 
        private static bool GetBooleanValue(NameValueCollection configurationValues, string valueName, bool defaultValue) {
            string s = configurationValues[valueName];
            if (s == null)
                return defaultValue;
            configurationValues.Remove(valueName);
            if (s == "true")
                return true;
            if (s == "false")
                return false;
            throw new ConfigurationErrorsException(SR.GetString(SR.Config_invalid_boolean_attribute, valueName));
        }
 
        [SuppressMessage("Microsoft.Cryptographic.Standard", "CA5353:TripleDESCannotBeUsed", Justification = @"AES is the default. For legacy compat, 3DES must be explicitly opted into.")]
        private SymmetricAlgorithm GetSymAlgorithmProvider() {
            SymmetricAlgorithm symAlg;
 
            if (UseFIPS) {
                // AesCryptoServiceProvider implementation is FIPS certified
                symAlg = new AesCryptoServiceProvider();
            }
            else {
                // Use the 3DES. FIPS obsolated 3DES
                symAlg = new TripleDESCryptoServiceProvider();
            }
 
            return symAlg;
        }
 
        private EncryptionMethod GetSymEncryptionMethod() {
            return UseFIPS ? new EncryptionMethod(EncryptedXml.XmlEncAES256Url) :
                             new EncryptionMethod(EncryptedXml.XmlEncTripleDESUrl);
        }
    }
}