File: System\ServiceModel\Security\SecurityAppliedMessage.cs
Web Access
Project: src\src\System.ServiceModel.Primitives\src\System.ServiceModel.Primitives.csproj (System.ServiceModel.Primitives)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.IO;
using System.Runtime;
using System.ServiceModel.Channels;
using System.Threading.Tasks;
using System.Xml;
 
using IPrefixGenerator = System.IdentityModel.IPrefixGenerator;
 
namespace System.ServiceModel.Security
{
    internal sealed class SecurityAppliedMessage : DelegatingMessage
    {
        private bool _bodyIdInserted;
        private string _bodyPrefix = MessageStrings.Prefix;
        private XmlBuffer _fullBodyBuffer;
        private XmlAttributeHolder[] _bodyAttributes;
        private bool _delayedApplicationHandled;
        private BodyState _state = BodyState.Created;
        private readonly SendSecurityHeader _securityHeader;
#pragma warning disable CS0649 // Field is never assign to
        private MemoryStream _startBodyFragment;
        private MemoryStream _endBodyFragment;
#pragma warning restore CS0649 // Field is never assign to
        private byte[] _fullBodyFragment;
        private int _fullBodyFragmentLength;
 
        public SecurityAppliedMessage(Message messageToProcess, SendSecurityHeader securityHeader, bool signBody, bool encryptBody)
            : base(messageToProcess)
        {
            Fx.Assert(!(messageToProcess is SecurityAppliedMessage), "SecurityAppliedMessage should not be wrapped");
            _securityHeader = securityHeader;
            BodyProtectionMode = MessagePartProtectionModeHelper.GetProtectionMode(signBody, encryptBody, securityHeader.SignThenEncrypt);
        }
 
        public string BodyId { get; private set; }
 
        public MessagePartProtectionMode BodyProtectionMode { get; }
 
        private Exception CreateBadStateException(string operation)
        {
            return new InvalidOperationException(SRP.Format(SRP.MessageBodyOperationNotValidInBodyState, operation, _state));
        }
 
        private void EnsureUniqueSecurityApplication()
        {
            if (_delayedApplicationHandled)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SRP.DelayedSecurityApplicationAlreadyCompleted));
            }
 
            _delayedApplicationHandled = true;
        }
 
        protected override void OnBodyToString(XmlDictionaryWriter writer)
        {
            if (_state == BodyState.Created || _fullBodyFragment != null)
            {
                base.OnBodyToString(writer);
            }
            else
            {
                OnWriteBodyContents(writer);
            }
        }
 
        protected override void OnClose()
        {
            try
            {
                InnerMessage.Close();
            }
            finally
            {
                _fullBodyBuffer = null;
                _bodyAttributes = null;
                _state = BodyState.Disposed;
            }
        }
 
        protected override void OnWriteStartBody(XmlDictionaryWriter writer)
        {
            if (_startBodyFragment != null || _fullBodyFragment != null)
            {
                WriteStartInnerMessageWithId(writer);
                return;
            }
 
            switch (_state)
            {
                case BodyState.Created:
                case BodyState.Encrypted:
                    InnerMessage.WriteStartBody(writer);
                    return;
                case BodyState.Signed:
                case BodyState.EncryptedThenSigned:
                    XmlDictionaryReader reader = _fullBodyBuffer.GetReader(0);
                    writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
                    writer.WriteAttributes(reader, false);
                    reader.Close();
                    return;
                case BodyState.SignedThenEncrypted:
                    writer.WriteStartElement(_bodyPrefix, XD.MessageDictionary.Body, Version.Envelope.DictionaryNamespace);
                    if (_bodyAttributes != null)
                    {
                        XmlAttributeHolder.WriteAttributes(_bodyAttributes, writer);
                    }
                    return;
                default:
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreateBadStateException(nameof(OnWriteStartBody)));
            }
        }
 
        protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
        {
            switch (_state)
            {
                case BodyState.Created:
                    InnerMessage.WriteBodyContents(writer);
                    return;
                case BodyState.Signed:
                case BodyState.EncryptedThenSigned:
                    XmlDictionaryReader reader = _fullBodyBuffer.GetReader(0);
                    reader.ReadStartElement();
                    while (reader.NodeType != XmlNodeType.EndElement)
                    {
                        writer.WriteNode(reader, false);
                    }
 
                    reader.ReadEndElement();
                    reader.Close();
                    return;
                case BodyState.Encrypted:
                case BodyState.SignedThenEncrypted:
                    throw ExceptionHelper.PlatformNotSupported();
                default:
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreateBadStateException(nameof(OnWriteBodyContents)));
            }
        }
 
        protected override async Task OnWriteBodyContentsAsync(XmlDictionaryWriter writer)
        {
            switch (_state)
            {
                case BodyState.Created:
                    await InnerMessage.WriteBodyContentsAsync(writer);
                    return;
                case BodyState.Signed:
                case BodyState.EncryptedThenSigned:
                    XmlDictionaryReader reader = _fullBodyBuffer.GetReader(0);
                    reader.ReadStartElement();
                    while (reader.NodeType != XmlNodeType.EndElement)
                    {
                        await writer.WriteNodeAsync(reader, false);
                    }
 
                    reader.ReadEndElement();
                    reader.Close();
                    return;
                case BodyState.Encrypted:
                case BodyState.SignedThenEncrypted:
                    throw ExceptionHelper.PlatformNotSupported();
                default:
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreateBadStateException(nameof(OnWriteBodyContentsAsync)));
            }
        }
 
        protected override void OnWriteMessage(XmlDictionaryWriter writer)
        {
            // For Kerb one shot, the channel binding will be need to be fished out of the message, cached and added to the
            // token before calling ISC.
 
            AttachChannelBindingTokenIfFound();
 
            EnsureUniqueSecurityApplication();
 
            MessagePrefixGenerator prefixGenerator = new MessagePrefixGenerator(writer);
            _securityHeader.StartSecurityApplication();
 
            Headers.Add(_securityHeader);
 
            InnerMessage.WriteStartEnvelope(writer);
 
            Headers.RemoveAt(Headers.Count - 1);
 
            _securityHeader.ApplyBodySecurity(writer, prefixGenerator);
 
            InnerMessage.WriteStartHeaders(writer);
            _securityHeader.ApplySecurityAndWriteHeaders(Headers, writer, prefixGenerator);
 
            _securityHeader.RemoveSignatureEncryptionIfAppropriate();
 
            _securityHeader.CompleteSecurityApplication();
            _securityHeader.WriteHeader(writer, Version);
            writer.WriteEndElement();
 
            if (_fullBodyFragment != null)
            {
                ((IFragmentCapableXmlDictionaryWriter)writer).WriteFragment(_fullBodyFragment, 0, _fullBodyFragmentLength);
            }
            else
            {
                if (_startBodyFragment != null)
                {
                    ((IFragmentCapableXmlDictionaryWriter)writer).WriteFragment(_startBodyFragment.GetBuffer(), 0, (int)_startBodyFragment.Length);
                }
                else
                {
                    OnWriteStartBody(writer);
                }
 
                OnWriteBodyContents(writer);
 
                if (_endBodyFragment != null)
                {
                    ((IFragmentCapableXmlDictionaryWriter)writer).WriteFragment(_endBodyFragment.GetBuffer(), 0, (int)_endBodyFragment.Length);
                }
                else
                {
                    writer.WriteEndElement();
                }
            }
 
            writer.WriteEndElement();
        }
 
        public override async Task OnWriteMessageAsync(XmlDictionaryWriter writer)
        {
            // For Kerb one shot, the channel binding will be need to be fished out of the message, cached and added to the
            // token before calling ISC.
 
            AttachChannelBindingTokenIfFound();
 
            EnsureUniqueSecurityApplication();
 
            MessagePrefixGenerator prefixGenerator = new MessagePrefixGenerator(writer);
            _securityHeader.StartSecurityApplication();
 
            Headers.Add(_securityHeader);
 
            InnerMessage.WriteStartEnvelope(writer);
 
            Headers.RemoveAt(Headers.Count - 1);
 
            _securityHeader.ApplyBodySecurity(writer, prefixGenerator);
 
            InnerMessage.WriteStartHeaders(writer);
            _securityHeader.ApplySecurityAndWriteHeaders(Headers, writer, prefixGenerator);
 
            _securityHeader.RemoveSignatureEncryptionIfAppropriate();
 
            _securityHeader.CompleteSecurityApplication();
            _securityHeader.WriteHeader(writer, Version);
            await writer.WriteEndElementAsync();
 
            if (_fullBodyFragment != null)
            {
                ((IFragmentCapableXmlDictionaryWriter)writer).WriteFragment(_fullBodyFragment, 0, _fullBodyFragmentLength);
            }
            else
            {
                if (_startBodyFragment != null)
                {
                    ((IFragmentCapableXmlDictionaryWriter)writer).WriteFragment(_startBodyFragment.GetBuffer(), 0, (int)_startBodyFragment.Length);
                }
                else
                {
                    OnWriteStartBody(writer);
                }
 
                await OnWriteBodyContentsAsync(writer);
 
                if (_endBodyFragment != null)
                {
                    ((IFragmentCapableXmlDictionaryWriter)writer).WriteFragment(_endBodyFragment.GetBuffer(), 0, (int)_endBodyFragment.Length);
                }
                else
                {
                    writer.WriteEndElement();
                }
            }
 
            await writer.WriteEndElementAsync();
        }
 
        private void AttachChannelBindingTokenIfFound()
        {
            // Only valid when using a TokenParameter of type KerberosSecurityTokenParameters which isn't currently supported
        }
 
        private void SetBodyId()
        {
            BodyId = InnerMessage.GetBodyAttribute(
                UtilityStrings.IdAttribute,
                _securityHeader.StandardsManager.IdManager.DefaultIdNamespaceUri);
            if (BodyId == null)
            {
                BodyId = _securityHeader.GenerateId();
                _bodyIdInserted = true;
            }
        }
 
        public void WriteBodyToSign(Stream canonicalStream)
        {
            SetBodyId();
 
            _fullBodyBuffer = new XmlBuffer(int.MaxValue);
            XmlDictionaryWriter canonicalWriter = _fullBodyBuffer.OpenSection(XmlDictionaryReaderQuotas.Max);
            canonicalWriter.StartCanonicalization(canonicalStream, false, null);
            WriteInnerMessageWithId(canonicalWriter);
            canonicalWriter.EndCanonicalization();
            canonicalWriter.Flush();
            _fullBodyBuffer.CloseSection();
            _fullBodyBuffer.Close();
 
            _state = BodyState.Signed;
        }
 
        public void WriteBodyToSignWithFragments(Stream stream, bool includeComments, string[] inclusivePrefixes, XmlDictionaryWriter writer)
        {
            IFragmentCapableXmlDictionaryWriter fragmentingWriter = (IFragmentCapableXmlDictionaryWriter)writer;
 
            SetBodyId();
            BufferedOutputStream fullBodyFragment = new BufferManagerOutputStream(SRP.XmlBufferQuotaExceeded, 1024, int.MaxValue, _securityHeader.StreamBufferManager);
            writer.StartCanonicalization(stream, includeComments, inclusivePrefixes);
            fragmentingWriter.StartFragment(fullBodyFragment, false);
            WriteStartInnerMessageWithId(writer);
            InnerMessage.WriteBodyContents(writer);
            writer.WriteEndElement();
            fragmentingWriter.EndFragment();
            writer.EndCanonicalization();
 
            _fullBodyFragment = fullBodyFragment.ToArray(out _fullBodyFragmentLength);
 
            _state = BodyState.Signed;
        }
 
        private void WriteInnerMessageWithId(XmlDictionaryWriter writer)
        {
            WriteStartInnerMessageWithId(writer);
            InnerMessage.WriteBodyContents(writer);
            writer.WriteEndElement();
        }
 
        private void WriteStartInnerMessageWithId(XmlDictionaryWriter writer)
        {
            InnerMessage.WriteStartBody(writer);
            if (_bodyIdInserted)
            {
                _securityHeader.StandardsManager.IdManager.WriteIdAttribute(writer, BodyId);
            }
        }
 
        private enum BodyState
        {
            Created,
            Signed,
            SignedThenEncrypted,
            EncryptedThenSigned,
            Encrypted,
            Disposed,
        }
 
        private sealed class MessagePrefixGenerator : IPrefixGenerator
        {
            private XmlWriter _writer;
 
            public MessagePrefixGenerator(XmlWriter writer)
            {
                _writer = writer;
            }
 
            public string GetPrefix(string namespaceUri, int depth, bool isForAttribute)
            {
                return _writer.LookupPrefix(namespaceUri);
            }
        }
    }
}