using System; using System.Collections.Specialized; using System.Configuration; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.Xml; using System.Xml; namespace MyApp.Core.Security { /// /// Class Pkcs12ProtectedConfigurationProvider. /// /// public class Pkcs12ProtectedConfigurationProvider : ProtectedConfigurationProvider { private string thumbprint; private StoreLocation storeLocation; /// /// Decrypts the XML node passed to it. /// /// The XmlNode to decrypt. /// public override XmlNode Decrypt(XmlNode encryptedNode) { var document = new XmlDocument(); // Get the RSA private key. This key will decrypt // a symmetric key that was embedded in the XML document. var cryptoServiceProvider = GetCryptoServiceProvider(false); document.PreserveWhitespace = true; document.LoadXml(encryptedNode.OuterXml); var xml = new EncryptedXml(document); // Add a key-name mapping.This method can only decrypt documents // that present the specified key name. xml.AddKeyNameMapping("rsaKey", cryptoServiceProvider); xml.DecryptDocument(); cryptoServiceProvider.Clear(); return document.DocumentElement; } /// /// Encrypts the XML node passed to it. /// /// The XmlNode to encrypt. /// public override XmlNode Encrypt(XmlNode node) { // Get the RSA public key to encrypt the node. This key will encrypt // a symmetric key, which will then be encryped in the XML document. var cryptoServiceProvider = GetCryptoServiceProvider(true); // Create an XML document and load the node to be encrypted in it. var document = new XmlDocument(); document.PreserveWhitespace = true; document.LoadXml("" + node.OuterXml + ""); // Create a new instance of the EncryptedXml class // and use it to encrypt the XmlElement with the // a new random symmetric key. var xml = new EncryptedXml(document); var documentElement = document.DocumentElement; SymmetricAlgorithm symmetricAlgorithm = new RijndaelManaged { // Create a 192 bit random key. Key = GetRandomKey() }; symmetricAlgorithm.GenerateIV(); symmetricAlgorithm.Padding = PaddingMode.PKCS7; var buffer = xml.EncryptData(documentElement, symmetricAlgorithm, true); // Construct an EncryptedData object and populate // it with the encryption information. var encryptedData = new EncryptedData { Type = EncryptedXml.XmlEncElementUrl, // Create an EncryptionMethod element so that the // receiver knows which algorithm to use for decryption. EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncAES192Url), KeyInfo = new KeyInfo() }; // Encrypt the session key and add it to an EncryptedKey element. var encryptedKey = new EncryptedKey { EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncRSA15Url), KeyInfo = new KeyInfo(), CipherData = new CipherData { CipherValue = EncryptedXml.EncryptKey(symmetricAlgorithm.Key, cryptoServiceProvider, false) } }; var clause = new KeyInfoName { Value = "rsaKey" }; // Add the encrypted key to the EncryptedData object. encryptedKey.KeyInfo.AddClause(clause); var key2 = new KeyInfoEncryptedKey(encryptedKey); encryptedData.KeyInfo.AddClause(key2); encryptedData.CipherData = new CipherData { CipherValue = buffer }; // Replace the element from the original XmlDocument // object with the EncryptedData element. EncryptedXml.ReplaceElement(documentElement, encryptedData, true); foreach (XmlNode node2 in document.ChildNodes) { if (node2.NodeType == XmlNodeType.Element) { foreach (XmlNode node3 in node2.ChildNodes) { if (node3.NodeType == XmlNodeType.Element) { return node3; } } } } return null; } /// /// Initializes the provider with default settings. /// /// /// A NameValueCollection collection of values to use /// when initializing the object. This must include a thumbprint value for the thumbprint of /// the certificate used to encrypt the configuration section. /// public override void Initialize(string name, NameValueCollection configurationValues) { base.Initialize(name, configurationValues); if (configurationValues["thumbprint"] == null || configurationValues["thumbprint"].Length == 0) { throw new ApplicationException("thumbprint not set in the configuration"); } this.thumbprint = configurationValues["thumbprint"]; try { this.storeLocation = (StoreLocation)Enum.Parse(typeof(StoreLocation), configurationValues["storeLocation"]); } catch (Exception ex) { throw new Exception(@"storeLocation must be ""LocalMachine"" or ""CurrentUser"".", ex); } } /// /// Get certificate from the Local Machine store, based on the given thumbprint /// /// The thumbnail of the certificate used to encrypt the configuration file. /// This can be CurrentUser or LocalMachine. Generally speaking Azure uses CurrentUser and Windows IIS uses LocalMachine. /// /// The certificate, with private key, must be accessible in the indicated store location. private X509Certificate2 GetCertificate(string thumbprint, StoreLocation storeLocation) { var store = new X509Store(StoreName.My, storeLocation); X509Certificate2Collection certificates = null; store.Open(OpenFlags.ReadOnly); try { X509Certificate2 result = null; certificates = store.Certificates; for (var i = 0; i < certificates.Count; i++) { var cert = certificates[i]; if (cert.Thumbprint.ToLower().CompareTo(thumbprint.ToLower()) == 0) { result = new X509Certificate2(cert); return result; } } if (result == null) { throw new ApplicationException(string.Format("No certificate was found for thumbprint {0}", thumbprint)); } return null; } finally { if (certificates != null) { for (var i = 0; i < certificates.Count; i++) { var cert = certificates[i]; cert.Reset(); } } store.Close(); } } /// /// Get either the public key for encrypting configuration sections or the private key to decrypt them. /// /// /// private RSACryptoServiceProvider GetCryptoServiceProvider(bool IsEncryption) { RSACryptoServiceProvider provider; var cert = GetCertificate(this.thumbprint, this.storeLocation); if (IsEncryption) { provider = (RSACryptoServiceProvider)cert.PublicKey.Key; } else { provider = (RSACryptoServiceProvider)cert.PrivateKey; } return provider; } private byte[] GetRandomKey() { var data = new byte[0x18]; new RNGCryptoServiceProvider().GetBytes(data); return data; } } }