File: System\Security\Cryptography\Xml\XmlDsigEnvelopedSignatureTransform.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography.Xml\src\System.Security.Cryptography.Xml.csproj (System.Security.Cryptography.Xml)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.IO;
using System.Xml;
 
namespace System.Security.Cryptography.Xml
{
    public class XmlDsigEnvelopedSignatureTransform : Transform
    {
        private readonly Type[] _inputTypes = { typeof(Stream), typeof(XmlNodeList), typeof(XmlDocument) };
        private readonly Type[] _outputTypes = { typeof(XmlNodeList), typeof(XmlDocument) };
        private XmlNodeList? _inputNodeList;
        private readonly bool _includeComments;
        private XmlNamespaceManager? _nsm;
        private XmlDocument? _containingDocument;
        private int _signaturePosition;
 
        internal int SignaturePosition
        {
            set { _signaturePosition = value; }
        }
 
        public XmlDsigEnvelopedSignatureTransform()
        {
            Algorithm = SignedXml.XmlDsigEnvelopedSignatureTransformUrl;
        }
 
        /// <internalonly/>
        public XmlDsigEnvelopedSignatureTransform(bool includeComments)
        {
            _includeComments = includeComments;
            Algorithm = SignedXml.XmlDsigEnvelopedSignatureTransformUrl;
        }
 
        public override Type[] InputTypes
        {
            get { return _inputTypes; }
        }
 
        public override Type[] OutputTypes
        {
            get { return _outputTypes; }
        }
 
        // An enveloped signature has no inner XML elements
        public override void LoadInnerXml(XmlNodeList nodeList)
        {
            if (nodeList != null && nodeList.Count > 0)
                throw new CryptographicException(SR.Cryptography_Xml_UnknownTransform);
        }
 
        // An enveloped signature has no inner XML elements
        protected override XmlNodeList? GetInnerXml()
        {
            return null;
        }
 
        public override void LoadInput(object obj)
        {
            if (obj is Stream)
            {
                LoadStreamInput((Stream)obj);
                return;
            }
            if (obj is XmlNodeList)
            {
                LoadXmlNodeListInput((XmlNodeList)obj);
                return;
            }
            if (obj is XmlDocument)
            {
                LoadXmlDocumentInput((XmlDocument)obj);
                return;
            }
        }
 
        private void LoadStreamInput(Stream stream)
        {
            XmlDocument doc = new XmlDocument();
            doc.PreserveWhitespace = true;
            XmlResolver resolver = ResolverSet ? _xmlResolver : XmlResolverHelper.GetThrowingResolver();
            XmlReader xmlReader = Utils.PreProcessStreamInput(stream, resolver, BaseURI!);
            doc.Load(xmlReader);
            _containingDocument = doc;
            if (_containingDocument == null)
                throw new CryptographicException(SR.Cryptography_Xml_EnvelopedSignatureRequiresContext);
            _nsm = new XmlNamespaceManager(_containingDocument.NameTable);
            _nsm.AddNamespace("dsig", SignedXml.XmlDsigNamespaceUrl);
        }
 
        private void LoadXmlNodeListInput(XmlNodeList nodeList)
        {
            if (nodeList is null)
            {
                throw new ArgumentNullException(nameof(nodeList));
            }
 
            _containingDocument = Utils.GetOwnerDocument(nodeList);
            if (_containingDocument == null)
                throw new CryptographicException(SR.Cryptography_Xml_EnvelopedSignatureRequiresContext);
 
            _nsm = new XmlNamespaceManager(_containingDocument.NameTable);
            _nsm.AddNamespace("dsig", SignedXml.XmlDsigNamespaceUrl);
            _inputNodeList = nodeList;
        }
 
        private void LoadXmlDocumentInput(XmlDocument doc)
        {
            if (doc is null)
            {
                throw new ArgumentNullException(nameof(doc));
            }
 
            _containingDocument = doc;
            _nsm = new XmlNamespaceManager(_containingDocument.NameTable);
            _nsm.AddNamespace("dsig", SignedXml.XmlDsigNamespaceUrl);
        }
 
        public override object GetOutput()
        {
            if (_containingDocument == null)
                throw new CryptographicException(SR.Cryptography_Xml_EnvelopedSignatureRequiresContext);
 
            // If we have received an XmlNodeList as input
            if (_inputNodeList != null)
            {
                // If the position has not been set, then we don't want to remove any signature tags
                if (_signaturePosition == 0) return _inputNodeList;
                XmlNodeList? signatureList = _containingDocument.SelectNodes("//dsig:Signature", _nsm!);
                if (signatureList == null) return _inputNodeList;
 
                CanonicalXmlNodeList resultNodeList = new CanonicalXmlNodeList();
                foreach (XmlNode? node in _inputNodeList)
                {
                    if (node == null) continue;
                    // keep namespaces
                    if (Utils.IsXmlNamespaceNode(node) || Utils.IsNamespaceNode(node))
                    {
                        resultNodeList.Add(node);
                    }
                    else
                    {
                        // SelectSingleNode throws an exception for xmldecl PI for example, so we will just ignore those exceptions
                        try
                        {
                            // Find the nearest signature ancestor tag
                            XmlNode result = node.SelectSingleNode("ancestor-or-self::dsig:Signature[1]", _nsm!)!;
                            int position = 0;
                            foreach (XmlNode node1 in signatureList)
                            {
                                position++;
                                if (node1 == result) break;
                            }
                            if (result == null || position != _signaturePosition)
                            {
                                resultNodeList.Add(node);
                            }
                        }
                        catch { }
                    }
                }
                return resultNodeList;
            }
            // Else we have received either a stream or a document as input
            else
            {
                XmlNodeList? signatureList = _containingDocument.SelectNodes("//dsig:Signature", _nsm!);
                if (signatureList == null) return _containingDocument;
                if (signatureList.Count < _signaturePosition || _signaturePosition <= 0) return _containingDocument;
 
                // Remove the signature node with all its children nodes
                signatureList[_signaturePosition - 1]!.ParentNode!.RemoveChild(signatureList[_signaturePosition - 1]!);
                return _containingDocument;
            }
        }
 
        public override object GetOutput(Type type)
        {
            if (type == typeof(XmlNodeList) || type.IsSubclassOf(typeof(XmlNodeList)))
            {
                _inputNodeList ??= Utils.AllDescendantNodes(_containingDocument!, true);
                return (XmlNodeList)GetOutput();
            }
            else if (type == typeof(XmlDocument) || type.IsSubclassOf(typeof(XmlDocument)))
            {
                if (_inputNodeList != null) throw new ArgumentException(SR.Cryptography_Xml_TransformIncorrectInputType, nameof(type));
                return (XmlDocument)GetOutput();
            }
            else
            {
                throw new ArgumentException(SR.Cryptography_Xml_TransformIncorrectInputType, nameof(type));
            }
        }
    }
}