File: system\security\cryptography\xml\encryptedxml.cs
Project: ndp\clr\src\managedlibraries\security\System.Security.csproj (System.Security)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
// <OWNER>Microsoft</OWNER>
// 
 
//
// EncryptedXml.cs
//
// 04/01/2002
//
 
namespace System.Security.Cryptography.Xml
{
    using System;
    using System.Collections;
    using System.IO;
    using System.Net;
    using System.Security;
    using System.Security.Cryptography;
    using System.Security.Cryptography.X509Certificates;
    using System.Security.Policy;
    using System.Text;
    using System.Xml;
 
    /// <summary>
    /// This exception helps catch the signed XML recursion limit error.
    /// This is being caught in the SignedXml class while computing the
    /// hash. ComputeHash can throw different kind of exceptions.
    /// This unique exception helps catch the recursion limit issue.
    /// </summary>
    [Serializable]
    internal class CryptoSignedXmlRecursionException : XmlException {
        public CryptoSignedXmlRecursionException() : base() { }
        public CryptoSignedXmlRecursionException(string message) : base(message) { }
        public CryptoSignedXmlRecursionException(string message, System.Exception inner) : base(message, inner) { }
        // A constructor is needed for serialization when an 
        // exception propagates from a remoting server to the client.  
        protected CryptoSignedXmlRecursionException(System.Runtime.Serialization.SerializationInfo info,
        System.Runtime.Serialization.StreamingContext context) { }
    }
 
    [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)]
    public class EncryptedXml {
 
        //
        // public constant Url identifiers used within the XML Encryption classes
        //
 
        public const string XmlEncNamespaceUrl = "http://www.w3.org/2001/04/xmlenc#";
        public const string XmlEncElementUrl = "http://www.w3.org/2001/04/xmlenc#Element";
        public const string XmlEncElementContentUrl = "http://www.w3.org/2001/04/xmlenc#Content";
        public const string XmlEncEncryptedKeyUrl = "http://www.w3.org/2001/04/xmlenc#EncryptedKey";
 
        //
        // Symmetric Block Encryption
        //
 
        public const string XmlEncDESUrl = "http://www.w3.org/2001/04/xmlenc#des-cbc";
        public const string XmlEncTripleDESUrl = "http://www.w3.org/2001/04/xmlenc#tripledes-cbc";
        public const string XmlEncAES128Url = "http://www.w3.org/2001/04/xmlenc#aes128-cbc";
        public const string XmlEncAES256Url = "http://www.w3.org/2001/04/xmlenc#aes256-cbc";
        public const string XmlEncAES192Url = "http://www.w3.org/2001/04/xmlenc#aes192-cbc";
 
        //
        // Key Transport
        //
 
        public const string XmlEncRSA15Url = "http://www.w3.org/2001/04/xmlenc#rsa-1_5";
        public const string XmlEncRSAOAEPUrl = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p";
 
        //
        // Symmetric Key Wrap
        //
 
        public const string XmlEncTripleDESKeyWrapUrl = "http://www.w3.org/2001/04/xmlenc#kw-tripledes";
        public const string XmlEncAES128KeyWrapUrl = "http://www.w3.org/2001/04/xmlenc#kw-aes128";
        public const string XmlEncAES256KeyWrapUrl = "http://www.w3.org/2001/04/xmlenc#kw-aes256";
        public const string XmlEncAES192KeyWrapUrl = "http://www.w3.org/2001/04/xmlenc#kw-aes192";
 
        //
        // Message Digest
        //
 
        public const string XmlEncSHA256Url = "http://www.w3.org/2001/04/xmlenc#sha256";
        public const string XmlEncSHA512Url = "http://www.w3.org/2001/04/xmlenc#sha512";
 
        //
        // private members
        //
 
        private XmlDocument m_document;
        private Evidence m_evidence;
        private XmlResolver m_xmlResolver;
        // hash table defining the key name mapping
        private const int m_capacity = 4; // 4 is a reasonable capacity for
                                          // the key name mapping hash table
        private Hashtable m_keyNameMapping;
        private PaddingMode m_padding;
        private CipherMode m_mode;
        private Encoding m_encoding;
        private string m_recipient;
        private int m_xmlDsigSearchDepthCounter = 0;
        private int m_xmlDsigSearchDepth;
 
        //
        // public constructors
        //
        public EncryptedXml () : this (new XmlDocument()) {}
 
        public EncryptedXml (XmlDocument document) : this (document, null) {}
 
        public EncryptedXml (XmlDocument document, Evidence evidence) {
            m_document = document;
            m_evidence = evidence;
            m_xmlResolver = null;
            // set the default padding to ISO-10126
            m_padding = PaddingMode.ISO10126;
            // set the default cipher mode to CBC
            m_mode = CipherMode.CBC;
            // By default the encoding is going to be UTF8
            m_encoding = Encoding.UTF8;
            m_keyNameMapping = new Hashtable(m_capacity);
            m_xmlDsigSearchDepth = Utils.GetXmlDsigSearchDepth();
        }
 
        /// <summary>
        /// This mentod validates the m_xmlDsigSearchDepthCounter counter
        /// if the counter is over the limit defined by admin or developer.
        /// </summary>
        /// <returns>returns true if the limit has reached otherwise false</returns>
        private bool IsOverXmlDsigRecursionLimit() {
            if (m_xmlDsigSearchDepthCounter > XmlDSigSearchDepth) {
                return true;
            }
            return false;
        }
 
        /// <summary>
        /// Gets / Sets the max limit for recursive search of encryption key in signed XML
        /// </summary>
        public int XmlDSigSearchDepth {
            get {
                return m_xmlDsigSearchDepth;
            }
            set {
                m_xmlDsigSearchDepth = value;
            }
        }
 
        //
        // public properties
        //
 
        // The evidence of the document being loaded: will be used to resolve external URIs
        public Evidence DocumentEvidence {
            get { return m_evidence; }
            set { m_evidence = value; }
        }
 
        // The resolver to use for external entities
        public XmlResolver Resolver {
            get { return m_xmlResolver; }
            set { m_xmlResolver = value; }
        }
 
        // The padding to be used. XML Encryption uses ISO 10126
        // but it's nice to provide a way to extend this to include other forms of paddings
        public PaddingMode Padding {
            get { return m_padding; }
            set { m_padding = value; }
        }
 
        // The cipher mode to be used. XML Encryption uses CBC padding
        // but it's nice to provide a way to extend this to include other cipher modes
        public CipherMode Mode {
            get { return m_mode; }
            set { m_mode = value; }
        }
 
        // The encoding of the XML document
        public Encoding Encoding {
            get { return m_encoding; }
            set { m_encoding = value; }
        }
 
        // This is used to specify the EncryptedKey elements that should be considered
        // when an EncyptedData references an EncryptedKey using a CarriedKeyName and Recipient
        public string Recipient {
            get {
                // an unspecified value for an XmlAttribute is String.Empty
                if (m_recipient == null)
                    m_recipient = String.Empty;
                return m_recipient;
            }
            set { m_recipient = value; }
        }
 
        //
        // private methods
        //
 
        private byte[] GetCipherValue (CipherData cipherData) {
            if (cipherData == null)
                throw new ArgumentNullException("cipherData");
 
            WebResponse response = null;
            Stream inputStream = null;
 
            if (cipherData.CipherValue != null) {
                return cipherData.CipherValue;
            } else if (cipherData.CipherReference != null) {
                if (cipherData.CipherReference.CipherValue != null)
                    return cipherData.CipherReference.CipherValue;
                Stream decInputStream = null;
                // See if the CipherReference is a local URI
                if (!Utils.GetLeaveCipherValueUnchecked() && cipherData.CipherReference.Uri == null){
                    throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_UriNotSupported"));
                }
                if (cipherData.CipherReference.Uri.Length == 0) {
                    // self referenced Uri
                    string baseUri = (m_document == null ? null : m_document.BaseURI);
                    TransformChain tc = cipherData.CipherReference.TransformChain;
                    if (!Utils.GetLeaveCipherValueUnchecked() && tc == null) {
                        throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_UriNotSupported"));
                    }
                    decInputStream = tc.TransformToOctetStream(m_document, m_xmlResolver, baseUri);
                } else if (cipherData.CipherReference.Uri[0] == '#') {
                    string idref = Utils.ExtractIdFromLocalUri(cipherData.CipherReference.Uri);
                    // Serialize 
                    if (Utils.GetLeaveCipherValueUnchecked()) {
                        inputStream = new MemoryStream(m_encoding.GetBytes(GetIdElement(m_document, idref).OuterXml));
                    }
                    else {
                        XmlElement idElem = GetIdElement(m_document, idref);
                        if (idElem == null || idElem.OuterXml == null) {
                            throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_UriNotSupported"));
                        }
                        inputStream = new MemoryStream(m_encoding.GetBytes(idElem.OuterXml));
                    }
 
                    string baseUri = (m_document == null ? null : m_document.BaseURI);
                    TransformChain tc = cipherData.CipherReference.TransformChain;
                    if (!Utils.GetLeaveCipherValueUnchecked() && tc == null) {
                        throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_UriNotSupported"));
                    }
                    decInputStream = tc.TransformToOctetStream(inputStream, m_xmlResolver, baseUri);
                } else {
                    DownloadCipherValue(cipherData, out inputStream, out decInputStream, out response);
                }
                // read the output stream into a memory stream
                byte[] cipherValue = null;
                using (MemoryStream ms = new MemoryStream()) {
                    Utils.Pump(decInputStream, ms);
                    cipherValue = ms.ToArray();
                    // Close the stream and return
                    if (response != null)
                        response.Close();
                    if (inputStream != null)
                        inputStream.Close();
                    decInputStream.Close();
                }
 
                // cache the cipher value for Perf reasons in case we call this routine twice
                cipherData.CipherReference.CipherValue = cipherValue;
                return cipherValue;
            }
 
            // Throw a CryptographicException if we were unable to retrieve the cipher data.
            throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_MissingCipherData"));
        }
 
        private void DownloadCipherValue (CipherData cipherData, out Stream inputStream, out Stream decInputStream, out WebResponse response) {
            // maybe a network stream, make sure we allow just what is needed!!
            PermissionSet ps = SecurityManager.GetStandardSandbox(m_evidence);
            ps.PermitOnly();
            WebRequest request = WebRequest.Create(cipherData.CipherReference.Uri);
            if (request == null) 
                throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_UriNotResolved"), cipherData.CipherReference.Uri);
            response = request.GetResponse();
            if (response == null) 
                throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_UriNotResolved"), cipherData.CipherReference.Uri);
            inputStream = response.GetResponseStream();
            if (inputStream == null) 
                throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_UriNotResolved"), cipherData.CipherReference.Uri);
            TransformChain tc = cipherData.CipherReference.TransformChain;
            decInputStream = tc.TransformToOctetStream(inputStream, m_xmlResolver, cipherData.CipherReference.Uri);
        }
 
        //
        // public virtual methods
        //
 
        // This describes how the application wants to associate id references to elements
        public virtual XmlElement GetIdElement (XmlDocument document, string idValue) {
            return SignedXml.DefaultGetIdElement(document, idValue);
        }
 
        // default behaviour is to look for the IV in the CipherValue
        public virtual byte[] GetDecryptionIV (EncryptedData encryptedData, string symmetricAlgorithmUri) {
            if (encryptedData == null)
                throw new ArgumentNullException("encryptedData");
 
            int initBytesSize = 0;
            // If the Uri is not provided by the application, try to get it from the EncryptionMethod
            if (symmetricAlgorithmUri == null) {
                if (encryptedData.EncryptionMethod == null)
                    throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_MissingAlgorithm"));
                symmetricAlgorithmUri = encryptedData.EncryptionMethod.KeyAlgorithm;
            }
            switch (symmetricAlgorithmUri) {
            case EncryptedXml.XmlEncDESUrl:
            case EncryptedXml.XmlEncTripleDESUrl:
                initBytesSize = 8;
                break;
            case EncryptedXml.XmlEncAES128Url:
            case EncryptedXml.XmlEncAES192Url:
            case EncryptedXml.XmlEncAES256Url:
                initBytesSize = 16;
                break;
            default:
                // The Uri is not supported.
                throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_UriNotSupported"));
            }
            byte[] IV = new byte[initBytesSize];
            byte[] cipherValue = GetCipherValue(encryptedData.CipherData);
            Buffer.BlockCopy(cipherValue, 0, IV, 0, IV.Length);
            return IV;
        }
 
        // default behaviour is to look for keys defined by an EncryptedKey clause
        // either directly or through a KeyInfoRetrievalMethod, and key names in the key mapping
        public virtual SymmetricAlgorithm GetDecryptionKey (EncryptedData encryptedData, string symmetricAlgorithmUri) {
            if (encryptedData == null)
                throw new ArgumentNullException("encryptedData");
 
            if (encryptedData.KeyInfo == null)
                return null;
            IEnumerator keyInfoEnum = encryptedData.KeyInfo.GetEnumerator();
            KeyInfoRetrievalMethod kiRetrievalMethod;
            KeyInfoName kiName;
            KeyInfoEncryptedKey kiEncKey;
            EncryptedKey ek = null;
 
            while (keyInfoEnum.MoveNext()) {
                kiName = keyInfoEnum.Current as KeyInfoName;
                if (kiName != null) {
                    // Get the decryption key from the key mapping
                    string keyName = kiName.Value;
                    if ((SymmetricAlgorithm) m_keyNameMapping[keyName] != null) 
                        return (SymmetricAlgorithm) m_keyNameMapping[keyName];
                    // try to get it from a CarriedKeyName
                    XmlNamespaceManager nsm = new XmlNamespaceManager(m_document.NameTable);
                    nsm.AddNamespace("enc", EncryptedXml.XmlEncNamespaceUrl);
                    XmlNodeList encryptedKeyList = m_document.SelectNodes("//enc:EncryptedKey", nsm);
                    if (encryptedKeyList != null) {
                        foreach (XmlNode encryptedKeyNode in encryptedKeyList) {
                            XmlElement encryptedKeyElement = encryptedKeyNode as XmlElement;
                            EncryptedKey ek1 = new EncryptedKey();
                            ek1.LoadXml(encryptedKeyElement);
                            if (ek1.CarriedKeyName == keyName && ek1.Recipient == this.Recipient) {
                                ek = ek1;
                                break;
                            }
                        }
                    }
                    break;
                }
                kiRetrievalMethod = keyInfoEnum.Current as KeyInfoRetrievalMethod;
                if (kiRetrievalMethod != null) { 
                    string idref = Utils.ExtractIdFromLocalUri(kiRetrievalMethod.Uri);
                    ek = new EncryptedKey();
                    ek.LoadXml(GetIdElement(m_document, idref));
                    break;
                }
                kiEncKey = keyInfoEnum.Current as KeyInfoEncryptedKey;
                if (kiEncKey != null) {
                    ek = kiEncKey.EncryptedKey;
                    break;
                }
            }
 
            // if we have an EncryptedKey, decrypt to get the symmetric key
            if (ek != null) {
                // now process the EncryptedKey, loop recursively 
                // If the Uri is not provided by the application, try to get it from the EncryptionMethod
                if (symmetricAlgorithmUri == null) {
                    if (encryptedData.EncryptionMethod == null)
                        throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_MissingAlgorithm"));
                    symmetricAlgorithmUri = encryptedData.EncryptionMethod.KeyAlgorithm;
                }
                byte[] key = DecryptEncryptedKey(ek);
                if (key == null)
                    throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_MissingDecryptionKey"));
 
                SymmetricAlgorithm symAlg = Utils.CreateFromName<SymmetricAlgorithm>(symmetricAlgorithmUri);
                if (symAlg == null) {
                    throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_MissingAlgorithm"));
                }
                symAlg.Key = key;
                return symAlg;
            }
            return null;
        }
 
        // Try to decrypt the EncryptedKey given the key mapping
        public virtual byte[] DecryptEncryptedKey (EncryptedKey encryptedKey) {
            if (encryptedKey == null)
                throw new ArgumentNullException("encryptedKey");
            if (encryptedKey.KeyInfo == null)
                return null;
 
            IEnumerator keyInfoEnum = encryptedKey.KeyInfo.GetEnumerator();
            KeyInfoName kiName;
            KeyInfoX509Data kiX509Data;
            KeyInfoRetrievalMethod kiRetrievalMethod;
            KeyInfoEncryptedKey kiEncKey;
            EncryptedKey ek = null;
            bool fOAEP = false;
 
            while (keyInfoEnum.MoveNext()) {
                kiName = keyInfoEnum.Current as KeyInfoName;
                if (kiName != null) {
                    // Get the decryption key from the key mapping
                    string keyName = kiName.Value;
                    Object kek = m_keyNameMapping[keyName];
                    if (kek != null) {
                        if (!Utils.GetLeaveCipherValueUnchecked() && (encryptedKey.CipherData == null || encryptedKey.CipherData.CipherValue == null)) {
                            throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_MissingAlgorithm"));
                        }
                        // kek is either a SymmetricAlgorithm or an RSA key, otherwise, we wouldn't be able to insert it in the hash table
                        if (kek is SymmetricAlgorithm)
                            return EncryptedXml.DecryptKey(encryptedKey.CipherData.CipherValue, (SymmetricAlgorithm) kek);
 
                        // kek is an RSA key: get fOAEP from the algorithm, default to false
                        fOAEP = (encryptedKey.EncryptionMethod != null && encryptedKey.EncryptionMethod.KeyAlgorithm == EncryptedXml.XmlEncRSAOAEPUrl);
                        return EncryptedXml.DecryptKey(encryptedKey.CipherData.CipherValue, (RSA) kek, fOAEP);
                    }
                    break;
                }
                kiX509Data = keyInfoEnum.Current as KeyInfoX509Data;
                if (kiX509Data != null) {
                    X509Certificate2Collection collection = Utils.BuildBagOfCerts(kiX509Data, CertUsageType.Decryption);
                    foreach (X509Certificate2 certificate in collection) {
                        using (RSA privateKey = certificate.GetRSAPrivateKey()) {
                            if (privateKey != null) {
                                if (!Utils.GetLeaveCipherValueUnchecked() && (encryptedKey.CipherData == null || encryptedKey.CipherData.CipherValue == null)) {
                                    throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_MissingAlgorithm"));
                                }
                                fOAEP = (encryptedKey.EncryptionMethod != null && encryptedKey.EncryptionMethod.KeyAlgorithm == EncryptedXml.XmlEncRSAOAEPUrl);
                                return EncryptedXml.DecryptKey(encryptedKey.CipherData.CipherValue, privateKey, fOAEP);
                            }
                        }
                    }
                    break;
                }
                kiRetrievalMethod = keyInfoEnum.Current as KeyInfoRetrievalMethod;
                if (kiRetrievalMethod != null) {
                    string idref = Utils.ExtractIdFromLocalUri(kiRetrievalMethod.Uri);
                    ek = new EncryptedKey();
                    ek.LoadXml(GetIdElement(m_document, idref));
                    try {
                        //Following checks if XML dsig processing is in loop and within the limit defined by machine
                        // admin or developer. Once the recursion depth crosses the defined limit it will throw exception.
                        m_xmlDsigSearchDepthCounter++;
                        if (IsOverXmlDsigRecursionLimit()) {
                            //Throw exception once recursion limit is hit. 
                            throw new CryptoSignedXmlRecursionException();
                        }
                        else {
                            return DecryptEncryptedKey(ek);
                        }
                    }
                    finally {
                        m_xmlDsigSearchDepthCounter--;
                    }
                }
                kiEncKey = keyInfoEnum.Current as KeyInfoEncryptedKey;
                if (kiEncKey != null) {
                    ek = kiEncKey.EncryptedKey;
                    // recursively process EncryptedKey elements
                    byte[] encryptionKey = DecryptEncryptedKey(ek);
                    if (encryptionKey != null) {
                        // this is a symmetric algorithm for sure
                        SymmetricAlgorithm symAlg = Utils.CreateFromName<SymmetricAlgorithm>(encryptedKey.EncryptionMethod.KeyAlgorithm);
                        if (symAlg == null) {
                            throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_MissingAlgorithm"));
                        }
                        symAlg.Key = encryptionKey;
                        if (!Utils.GetLeaveCipherValueUnchecked()  && (encryptedKey.CipherData == null || encryptedKey.CipherData.CipherValue == null)) {
                            throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_MissingAlgorithm"));
                        }
                        return EncryptedXml.DecryptKey(encryptedKey.CipherData.CipherValue, symAlg);
                    }
                }
            }
            return null;
        }
 
        //
        // public methods
        //
 
        // defines a key name mapping. Default behaviour is to require the key object
        // to be an RSA key or a SymmetricAlgorithm
        public void AddKeyNameMapping (string keyName, Object keyObject) {
            if (keyName == null)
                throw new ArgumentNullException("keyName");
            if (keyObject == null)
                throw new ArgumentNullException("keyObject");
 
            if (!(keyObject is SymmetricAlgorithm) && !(keyObject is RSA)) 
                throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_NotSupportedCryptographicTransform"));
            m_keyNameMapping.Add(keyName, keyObject);
        }
 
        public void ClearKeyNameMappings () {
            m_keyNameMapping.Clear();
        }
 
        // Encrypts the given element with the certificate specified. The certificate is added as
        // an X509Data KeyInfo to an EncryptedKey (AES session key) generated randomly.
        public EncryptedData Encrypt (XmlElement inputElement, X509Certificate2 certificate) {
            if (inputElement == null)
                throw new ArgumentNullException("inputElement");
            if (certificate == null)
                throw new ArgumentNullException("certificate");
 
            using (RSA rsaPublicKey = certificate.GetRSAPublicKey()) {
                if (rsaPublicKey == null)
                    throw new NotSupportedException(SecurityResources.GetResourceString("NotSupported_KeyAlgorithm"));
    
                // Create the EncryptedData object, using an AES-256 session key by default.
                EncryptedData ed = new EncryptedData();
                ed.Type = EncryptedXml.XmlEncElementUrl;
                ed.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncAES256Url);
    
                // Include the certificate in the EncryptedKey KeyInfo.
                EncryptedKey ek = new EncryptedKey();
                ek.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncRSA15Url);
                ek.KeyInfo.AddClause(new KeyInfoX509Data(certificate));
 
                // Create a random AES session key and encrypt it with the public key associated with the certificate.
                using (Aes aes = Aes.Create()) {
                    ek.CipherData.CipherValue = EncryptedXml.EncryptKey(aes.Key, rsaPublicKey, false);
 
                    // Encrypt the input element with the random session key that we've created above.
                    KeyInfoEncryptedKey kek = new KeyInfoEncryptedKey(ek);
                    ed.KeyInfo.AddClause(kek);
                    ed.CipherData.CipherValue = EncryptData(inputElement, aes, false);
                }
 
                return ed;
            }
        }
 
        // Encrypts the given element with the key name specified. A corresponding key name mapping 
        // has to be defined before calling this method. The key name is added as
        // a KeyNameInfo KeyInfo to an EncryptedKey (AES session key) generated randomly.
        public EncryptedData Encrypt (XmlElement inputElement, string keyName) {
            if (inputElement == null)
                throw new ArgumentNullException("inputElement");
            if (keyName == null)
                throw new ArgumentNullException("keyName");
 
            Object encryptionKey = null;
            if (m_keyNameMapping != null)
                encryptionKey = m_keyNameMapping[keyName];
 
            if (encryptionKey == null)
                throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_MissingEncryptionKey"));
 
            // kek is either a SymmetricAlgorithm or an RSA key, otherwise, we wouldn't be able to insert it in the hash table
            SymmetricAlgorithm symKey = encryptionKey as SymmetricAlgorithm;
            RSA rsa = encryptionKey as RSA;
 
            // Create the EncryptedData object, using an AES-256 session key by default.
            EncryptedData ed = new EncryptedData();
            ed.Type = EncryptedXml.XmlEncElementUrl;
            ed.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncAES256Url);
 
            // Include the key name in the EncryptedKey KeyInfo.
            string encryptionMethod = null;
            if (symKey == null) {
                encryptionMethod = EncryptedXml.XmlEncRSA15Url;
            } else if (symKey is TripleDES) {
                // CMS Triple DES Key Wrap
                encryptionMethod = EncryptedXml.XmlEncTripleDESKeyWrapUrl;
            } else if (symKey is Rijndael || symKey is Aes) {
                // FIPS AES Key Wrap
                switch (symKey.KeySize) {
                case 128:
                    encryptionMethod = EncryptedXml.XmlEncAES128KeyWrapUrl;
                    break;
                case 192:
                    encryptionMethod = EncryptedXml.XmlEncAES192KeyWrapUrl;
                    break;
                case 256:
                    encryptionMethod = EncryptedXml.XmlEncAES256KeyWrapUrl;
                    break;
                }
            } else {
                // throw an exception if the transform is not in the previous categories
                throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_NotSupportedCryptographicTransform"));
            }
            EncryptedKey ek = new EncryptedKey();
            ek.EncryptionMethod = new EncryptionMethod(encryptionMethod);
            ek.KeyInfo.AddClause(new KeyInfoName(keyName));
 
            // Create a random AES session key and encrypt it with the public key associated with the certificate.
            using (Aes aes = Aes.Create()) {
                ek.CipherData.CipherValue = symKey == null ?
                    EncryptedXml.EncryptKey(aes.Key, rsa, false) :
                    EncryptedXml.EncryptKey(aes.Key, symKey);
 
                // Encrypt the input element with the random session key that we've created above.
                KeyInfoEncryptedKey kek = new KeyInfoEncryptedKey(ek);
                ed.KeyInfo.AddClause(kek);
                ed.CipherData.CipherValue = EncryptData(inputElement, aes, false);
            }
 
            return ed;
        }
 
        // decrypts the document using the defined key mapping in GetDecryptionKey
        // The behaviour of this method can be extended because GetDecryptionKey is virtual
        // the document is decrypted in place
        public void DecryptDocument () {
            // Look for all EncryptedData elements and decrypt them
            XmlNamespaceManager nsm = new XmlNamespaceManager(m_document.NameTable);
            nsm.AddNamespace("enc", EncryptedXml.XmlEncNamespaceUrl);
            XmlNodeList encryptedDataList = m_document.SelectNodes("//enc:EncryptedData", nsm);
            if (encryptedDataList != null) {
                foreach (XmlNode encryptedDataNode in encryptedDataList) {
                    XmlElement encryptedDataElement = encryptedDataNode as XmlElement;
                    EncryptedData ed = new EncryptedData();
                    ed.LoadXml(encryptedDataElement);
                    SymmetricAlgorithm symAlg = GetDecryptionKey(ed, null);
                    if (symAlg == null)
                        throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_MissingDecryptionKey"));
                    byte[] decrypted = DecryptData(ed, symAlg);
                    ReplaceData(encryptedDataElement, decrypted);
                }
            }
        }
 
        // encrypts the supplied arbitrary data
        public byte[] EncryptData (byte[] plaintext, SymmetricAlgorithm symmetricAlgorithm) {
            if (plaintext == null)
                throw new ArgumentNullException("plaintext");
            if (symmetricAlgorithm == null)
                throw new ArgumentNullException("symmetricAlgorithm");
 
            // save the original symmetric algorithm
            CipherMode origMode = symmetricAlgorithm.Mode;
            PaddingMode origPadding = symmetricAlgorithm.Padding;
 
            byte[] cipher = null;
            try {
                symmetricAlgorithm.Mode = m_mode;
                symmetricAlgorithm.Padding = m_padding;
 
                ICryptoTransform enc = symmetricAlgorithm.CreateEncryptor();
                cipher = enc.TransformFinalBlock(plaintext, 0, plaintext.Length);
            } finally {
                // now restore the original symmetric algorithm
                symmetricAlgorithm.Mode = origMode;
                symmetricAlgorithm.Padding = origPadding;
            }
 
            byte[] output = null;
            if (m_mode == CipherMode.ECB) {
                output = cipher;
            } else {
                byte[] IV = symmetricAlgorithm.IV;
                output = new byte[cipher.Length + IV.Length];
                Buffer.BlockCopy(IV, 0, output, 0, IV.Length);
                Buffer.BlockCopy(cipher, 0, output, IV.Length, cipher.Length);
            }
            return output;
        }
 
        // encrypts the supplied input element
        public byte[] EncryptData (XmlElement inputElement, SymmetricAlgorithm symmetricAlgorithm, bool content) {
            if (inputElement == null)
                throw new ArgumentNullException("inputElement");
            if (symmetricAlgorithm == null)
                throw new ArgumentNullException("symmetricAlgorithm");
 
            byte[] plainText = (content ? m_encoding.GetBytes(inputElement.InnerXml) : m_encoding.GetBytes(inputElement.OuterXml));
            return EncryptData(plainText, symmetricAlgorithm);
        }
 
        // decrypts the supplied EncryptedData
        public byte[] DecryptData (EncryptedData encryptedData, SymmetricAlgorithm symmetricAlgorithm) {
            if (encryptedData == null)
                throw new ArgumentNullException("encryptedData");
            if (symmetricAlgorithm == null)
                throw new ArgumentNullException("symmetricAlgorithm");
 
            // get the cipher value and decrypt
            byte[] cipherValue = GetCipherValue(encryptedData.CipherData);
 
            // save the original symmetric algorithm
            CipherMode origMode = symmetricAlgorithm.Mode;
            PaddingMode origPadding = symmetricAlgorithm.Padding;
            byte[] origIV = symmetricAlgorithm.IV;
 
            // read the IV from cipherValue
            byte[] decryptionIV = null;
            if (m_mode != CipherMode.ECB)
                decryptionIV = GetDecryptionIV(encryptedData, null);
 
            byte[] output = null;
            try {
                int lengthIV = 0;
                if (decryptionIV != null) {
                    symmetricAlgorithm.IV = decryptionIV;
                    lengthIV = decryptionIV.Length;
                }
                symmetricAlgorithm.Mode = m_mode;
                symmetricAlgorithm.Padding = m_padding;
 
                ICryptoTransform dec = symmetricAlgorithm.CreateDecryptor();
                output = dec.TransformFinalBlock(cipherValue, lengthIV, cipherValue.Length - lengthIV);
            } finally {
                // now restore the original symmetric algorithm
                symmetricAlgorithm.Mode = origMode;
                symmetricAlgorithm.Padding = origPadding;
                symmetricAlgorithm.IV = origIV;
            }
 
            return output;
        }
 
        // This method replaces an EncryptedData element with the decrypted sequence of bytes
        public void ReplaceData (XmlElement inputElement, byte[] decryptedData) {
            if (inputElement == null)
                throw new ArgumentNullException ("inputElement");
            if (decryptedData == null)
                throw new ArgumentNullException ("decryptedData");
 
            XmlNode parent = inputElement.ParentNode;
            if (parent.NodeType == XmlNodeType.Document) {
                // We're replacing the root element, but we can't just wholesale replace the owner
                // document's InnerXml, since we need to preserve any other top-level XML elements (such as
                // comments or the XML entity declaration.  Instead, create a new document with the
                // decrypted XML, import it into the existing document, and replace just the root element.
                XmlDocument importDocument = new XmlDocument();
                importDocument.PreserveWhitespace = true;
                string decryptedString = m_encoding.GetString(decryptedData);
                using (StringReader sr = new StringReader(decryptedString))
                {
                    using (XmlReader xr = XmlReader.Create(sr, Utils.GetSecureXmlReaderSettings(m_xmlResolver)))
                    {
                        importDocument.Load(xr);
                    }
                }
 
                XmlNode importedNode = inputElement.OwnerDocument.ImportNode(importDocument.DocumentElement, true);
 
                parent.RemoveChild(inputElement);
                parent.AppendChild(importedNode);
            } else {
                XmlNode dummy = parent.OwnerDocument.CreateElement(parent.Prefix, parent.LocalName, parent.NamespaceURI);
 
                try {
                    parent.AppendChild(dummy);
 
                    // Replace the children of the dummy node with the sequence of bytes passed in.
                    // The string will be parsed into DOM objects in the context of the parent of the EncryptedData element.
                    dummy.InnerXml = m_encoding.GetString(decryptedData);
 
                    // Move the children of the dummy node up to the parent.
                    XmlNode child = dummy.FirstChild;
                    XmlNode sibling = inputElement.NextSibling;
 
                    XmlNode nextChild = null;
                    while (child != null) {
                        nextChild = child.NextSibling;
                        parent.InsertBefore(child, sibling);
                        child = nextChild;
                    }
                }
                finally {
                    // Remove the dummy element.
                    parent.RemoveChild(dummy);
                }
 
                // Remove the EncryptedData element
                parent.RemoveChild(inputElement);
            }
        }
 
        //
        // public static methods
        //
 
        // replaces the inputElement with the provided EncryptedData
        public static void ReplaceElement (XmlElement inputElement, EncryptedData encryptedData, bool content) {
            if (inputElement == null)
                throw new ArgumentNullException("inputElement");
            if (encryptedData == null)
                throw new ArgumentNullException("encryptedData");
 
            // First, get the XML representation of the EncryptedData object
            XmlElement elemED = encryptedData.GetXml(inputElement.OwnerDocument);
            switch (content) {
            case true:
                // remove all children of the input element
                Utils.RemoveAllChildren(inputElement);
                // then append the encrypted data as a child of the input element
                inputElement.AppendChild(elemED);
                break;
            case false:
                XmlNode parentNode = inputElement.ParentNode;
                // remove the input element from the containing document
                parentNode.ReplaceChild(elemED, inputElement);
                break;
            }
        }
 
        // wraps the supplied input key data using the provided symmetric algorithm
        public static byte[] EncryptKey (byte[] keyData, SymmetricAlgorithm symmetricAlgorithm) {
            if (keyData == null)
                throw new ArgumentNullException("keyData");
            if (symmetricAlgorithm == null)
                throw new ArgumentNullException("symmetricAlgorithm");
 
            if (symmetricAlgorithm is TripleDES) {
                // CMS Triple DES Key Wrap
                return SymmetricKeyWrap.TripleDESKeyWrapEncrypt(symmetricAlgorithm.Key, keyData);
            } else if (symmetricAlgorithm is Rijndael || symmetricAlgorithm is Aes) {
                // FIPS AES Key Wrap
                return SymmetricKeyWrap.AESKeyWrapEncrypt(symmetricAlgorithm.Key, keyData);
            }
            // throw an exception if the transform is not in the previous categories
            throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_NotSupportedCryptographicTransform"));
        }
 
 
        // encrypts the supplied input key data using an RSA key and specifies whether we want to use OAEP 
        // padding or PKCS#1 v1.5 padding as described in the PKCS specification
        public static byte[] EncryptKey (byte[] keyData, RSA rsa, bool useOAEP) {
            if (keyData == null)
                throw new ArgumentNullException("keyData");
            if (rsa == null)
                throw new ArgumentNullException("rsa");
 
            if (useOAEP) {
                RSAOAEPKeyExchangeFormatter rsaFormatter = new RSAOAEPKeyExchangeFormatter(rsa);
                return rsaFormatter.CreateKeyExchange(keyData);
            } else {
                RSAPKCS1KeyExchangeFormatter rsaFormatter = new RSAPKCS1KeyExchangeFormatter(rsa);
                return rsaFormatter.CreateKeyExchange(keyData);
            }
        }
 
        // decrypts the supplied wrapped key using the provided symmetric algorithm
        public static byte[] DecryptKey (byte[] keyData, SymmetricAlgorithm symmetricAlgorithm) {
            if (keyData == null)
                throw new ArgumentNullException("keyData");
            if (symmetricAlgorithm == null)
                throw new ArgumentNullException("symmetricAlgorithm");
 
            if (symmetricAlgorithm is TripleDES) {
                // CMS Triple DES Key Wrap
                return SymmetricKeyWrap.TripleDESKeyWrapDecrypt(symmetricAlgorithm.Key, keyData);
            } else if (symmetricAlgorithm is Rijndael || symmetricAlgorithm is Aes) {
                // FIPS AES Key Wrap
                return SymmetricKeyWrap.AESKeyWrapDecrypt(symmetricAlgorithm.Key, keyData);
            }
            // throw an exception if the transform is not in the previous categories
            throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_NotSupportedCryptographicTransform"));
        }
 
        // decrypts the supplied data using an RSA key and specifies whether we want to use OAEP 
        // padding or PKCS#1 v1.5 padding as described in the PKCS specification
        public static byte[] DecryptKey (byte[] keyData, RSA rsa, bool useOAEP) {
            if (keyData == null)
                throw new ArgumentNullException("keyData");
            if (rsa == null)
                throw new ArgumentNullException("rsa");
 
            if (useOAEP) {
                RSAOAEPKeyExchangeDeformatter rsaDeformatter = new RSAOAEPKeyExchangeDeformatter(rsa);
                return rsaDeformatter.DecryptKeyExchange(keyData);
            } else {
                RSAPKCS1KeyExchangeDeformatter rsaDeformatter = new RSAPKCS1KeyExchangeDeformatter(rsa);
                return rsaDeformatter.DecryptKeyExchange(keyData);
            }
        }
    }
}