Created
July 21, 2020 01:56
-
-
Save brendanmckenzie/1373fed141f7dcd808b08c58aa1a56c4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
{ | |
/// <summary> | |
/// Class Pkcs12ProtectedConfigurationProvider. | |
/// </summary> | |
/// <seealso cref="ProtectedConfigurationProvider" /> | |
public class Pkcs12ProtectedConfigurationProvider : ProtectedConfigurationProvider | |
{ | |
private string thumbprint; | |
private StoreLocation storeLocation; | |
/// <summary> | |
/// Decrypts the XML node passed to it. | |
/// </summary> | |
/// <param name="encryptedNode">The XmlNode to decrypt.</param> | |
/// <returns></returns> | |
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; | |
} | |
/// <summary> | |
/// Encrypts the XML node passed to it. | |
/// </summary> | |
/// <param name="node">The XmlNode to encrypt.</param> | |
/// <returns></returns> | |
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("<Data>" + node.OuterXml + "</Data>"); | |
// 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; | |
} | |
/// <summary> | |
/// Initializes the provider with default settings. | |
/// </summary> | |
/// <param name="name"></param> | |
/// <param name="configurationValues">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. | |
/// </param> | |
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); | |
} | |
} | |
/// <summary> | |
/// Get certificate from the Local Machine store, based on the given thumbprint | |
/// </summary> | |
/// <param name="thumbprint">The thumbnail of the certificate used to encrypt the configuration file.</param> | |
/// <param name="storeLocation">This can be CurrentUser or LocalMachine. Generally speaking Azure uses CurrentUser and Windows IIS uses LocalMachine.</param> | |
/// <returns></returns> | |
/// <remarks>The certificate, with private key, must be accessible in the indicated store location.</remarks> | |
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(); | |
} | |
} | |
/// <summary> | |
/// Get either the public key for encrypting configuration sections or the private key to decrypt them. | |
/// </summary> | |
/// <param name="IsEncryption"></param> | |
/// <returns></returns> | |
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; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment