File: System\IdentityModel\EnvelopedSignatureWriter.cs
Project: ndp\cdf\src\WCF\IdentityModel\System.IdentityModel.csproj (System.IdentityModel)
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
 
 
namespace System.IdentityModel
{
    using System;
    using System.Diagnostics;
    using System.IdentityModel.Selectors;
    using System.IdentityModel.Tokens;
    using System.IO;
    using System.Security.Cryptography;
    using System.Text;
    using System.Xml;
    using System.Runtime;
 
    /// <summary>
    /// Wraps a writer and generates a signature automatically when the envelope
    /// is written completely. By default the generated signature is inserted as
    /// the last element in the envelope. This can be modified by explicitily 
    /// calling WriteSignature to indicate the location inside the envelope where
    /// the signature should be inserted.
    /// </summary>
    public sealed class EnvelopedSignatureWriter : DelegatingXmlDictionaryWriter
    {
        DictionaryManager _dictionaryManager;
        XmlWriter _innerWriter;
        SigningCredentials _signingCreds;
        string _referenceId;
        SecurityTokenSerializer _tokenSerializer;
        HashStream _hashStream;
        HashAlgorithm _hashAlgorithm;
        int _elementCount;
        MemoryStream _signatureFragment;
        MemoryStream _endFragment;
        bool _hasSignatureBeenMarkedForInsert;
        MemoryStream _writerStream;
        MemoryStream _preCanonicalTracingStream;
        bool _disposed;
 
        /// <summary>
        /// Initializes an instance of <see cref="EnvelopedSignatureWriter"/>. The returned writer can be directly used
        /// to write the envelope. The signature will be automatically generated when 
        /// the envelope is completed.
        /// </summary>
        /// <param name="innerWriter">Writer to wrap/</param>
        /// <param name="signingCredentials">SigningCredentials to be used to generate the signature.</param>
        /// <param name="referenceId">The reference Id of the envelope.</param>
        /// <param name="securityTokenSerializer">SecurityTokenSerializer to serialize the signature KeyInfo.</param>
        /// <exception cref="ArgumentNullException">One of he input parameter is null.</exception>
        /// <exception cref="ArgumentException">The string 'referenceId' is either null or empty.</exception>
        public EnvelopedSignatureWriter(XmlWriter innerWriter, SigningCredentials signingCredentials, string referenceId, SecurityTokenSerializer securityTokenSerializer)
        {
            if (innerWriter == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("innerWriter");
            }
 
            if (signingCredentials == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("signingCredentials");
            }
 
            if (string.IsNullOrEmpty(referenceId))
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.ID0006), "referenceId"));
            }
 
            if (securityTokenSerializer == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("securityTokenSerializer");
            }
 
            // Remember the user's writer here. We need to finally write out the signed XML
            // into this writer.
            _dictionaryManager = new DictionaryManager();
            _innerWriter = innerWriter;
            _signingCreds = signingCredentials;
            _referenceId = referenceId;
            _tokenSerializer = securityTokenSerializer;
 
            _signatureFragment = new MemoryStream();
            _endFragment = new MemoryStream();
            _writerStream = new MemoryStream();
 
            XmlDictionaryWriter effectiveWriter = XmlDictionaryWriter.CreateTextWriter(_writerStream, Encoding.UTF8, false);
 
            // Initialize the base writer to the newly created writer. The user should write the XML
            // to this.
            base.InitializeInnerWriter(effectiveWriter);
            _hashAlgorithm = CryptoHelper.CreateHashAlgorithm(_signingCreds.DigestAlgorithm);
            _hashStream = new HashStream(_hashAlgorithm);
            base.InnerWriter.StartCanonicalization(_hashStream, false, null);
 
            //
            // Add tracing for the un-canonicalized bytes
            //
            if (DiagnosticUtility.ShouldTraceVerbose)
            {
                _preCanonicalTracingStream = new MemoryStream();
                base.InitializeTracingWriter(new XmlTextWriter(_preCanonicalTracingStream, Encoding.UTF8));
            }
        }
 
        private void ComputeSignature()
        {
            PreDigestedSignedInfo signedInfo = new PreDigestedSignedInfo(_dictionaryManager);
            signedInfo.AddEnvelopedSignatureTransform = true;
            signedInfo.CanonicalizationMethod = XD.ExclusiveC14NDictionary.Namespace.Value;
            signedInfo.SignatureMethod = _signingCreds.SignatureAlgorithm;
            signedInfo.DigestMethod = _signingCreds.DigestAlgorithm;
            signedInfo.AddReference(_referenceId, _hashStream.FlushHashAndGetValue(_preCanonicalTracingStream));
 
            SignedXml signedXml = new SignedXml(signedInfo, _dictionaryManager, _tokenSerializer);
            signedXml.ComputeSignature(_signingCreds.SigningKey);
            signedXml.Signature.KeyIdentifier = _signingCreds.SigningKeyIdentifier;
            signedXml.WriteTo(base.InnerWriter);
            ((IDisposable)_hashStream).Dispose();
            _hashStream = null;
        }
 
        private void OnEndRootElement()
        {
            if (!_hasSignatureBeenMarkedForInsert)
            {
                // Default case. Signature is added as the last child element.
                // We still have to compute the signature. Write end element as a different fragment.
 
                ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).StartFragment(_endFragment, false);
                base.WriteEndElement();
                ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).EndFragment();
            }
            else if (_hasSignatureBeenMarkedForInsert)
            {
                // Signature should be added to the middle between the start and element 
                // elements. Finish the end fragment and compute the signature and 
                // write the signature as a seperate fragment.
                base.WriteEndElement();
                ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).EndFragment();
            }
 
            // Stop Canonicalization.
            base.EndCanonicalization();
 
            // Compute signature and write it into a seperate fragment.
            ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).StartFragment(_signatureFragment, false);
            ComputeSignature();
            ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).EndFragment();
 
            // Put all fragments together. The fragment before the signature is already written into the writer.
            ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).WriteFragment(_signatureFragment.GetBuffer(), 0, (int)_signatureFragment.Length);
            ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).WriteFragment(_endFragment.GetBuffer(), 0, (int)_endFragment.Length);
 
            // _startFragment.Close();
            _signatureFragment.Close();
            _endFragment.Close();
 
            _writerStream.Position = 0;
            _hasSignatureBeenMarkedForInsert = false;
 
            // Write the signed stream to the writer provided by the user.
            // We are creating a Text Reader over a stream that we just wrote out. Hence, it is safe to 
            // create a XmlTextReader and not a XmlDictionaryReader.
            // Note: reader will close _writerStream on Dispose.
            XmlReader reader = XmlDictionaryReader.CreateTextReader(_writerStream, XmlDictionaryReaderQuotas.Max);
            reader.MoveToContent();
            _innerWriter.WriteNode(reader, false);
            _innerWriter.Flush();
            reader.Close();
            base.Close();
        }
 
        /// <summary>
        /// Sets the position of the signature within the envelope. Call this
        /// method while writing the envelope to indicate at which point the 
        /// signature should be inserted.
        /// </summary>
        public void WriteSignature()
        {
            base.Flush();
            if (_writerStream == null || _writerStream.Length == 0)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID6029)));
            }
 
            if (_signatureFragment.Length != 0)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID6030)));
            }
 
            Fx.Assert(_endFragment != null && _endFragment.Length == 0, SR.GetString(SR.ID8026));
 
            // Capture the remaing as a seperate fragment.
            ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).StartFragment(_endFragment, false);
 
            _hasSignatureBeenMarkedForInsert = true;
        }
 
        /// <summary>
        /// Overrides the base class implementation. When the last element of the envelope is written
        /// the signature is automatically computed over the envelope and the signature is inserted at
        /// the appropriate position, if WriteSignature was explicitly called or is inserted at the
        /// end of the envelope.
        /// </summary>
        public override void WriteEndElement()
        {
            _elementCount--;
            if (_elementCount == 0)
            {
                base.Flush();
                OnEndRootElement();
            }
            else
            {
                base.WriteEndElement();
            }
        }
 
        /// <summary>
        /// Overrides the base class implementation. When the last element of the envelope is written
        /// the signature is automatically computed over the envelope and the signature is inserted at
        /// the appropriate position, if WriteSignature was explicitly called or is inserted at the
        /// end of the envelope.
        /// </summary>
        public override void WriteFullEndElement()
        {
            _elementCount--;
            if (_elementCount == 0)
            {
                base.Flush();
                OnEndRootElement();
            }
            else
            {
                base.WriteFullEndElement();
            }
        }
 
        /// <summary>
        /// Overrides the base class. Writes the specified start tag and associates
        /// it with the given namespace.
        /// </summary>
        /// <param name="prefix">The namespace prefix of the element.</param>
        /// <param name="localName">The local name of the element.</param>
        /// <param name="ns">The namespace URI to associate with the element.</param>
        public override void WriteStartElement(string prefix, string localName, string ns)
        {
            _elementCount++;
            base.WriteStartElement(prefix, localName, ns);
        }
 
        #region IDisposable Members
 
        /// <summary>
        /// Releases the unmanaged resources used by the System.IdentityModel.Protocols.XmlSignature.EnvelopedSignatureWriter and optionally
        /// releases the managed resources.
        /// </summary>
        /// <param name="disposing">
        /// True to release both managed and unmanaged resources; false to release only unmanaged resources.
        /// </param>
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
 
            if (_disposed)
            {
                return;
            }
 
            if (disposing)
            {
                //
                // Free all of our managed resources
                //
                if (_hashStream != null)
                {
                    _hashStream.Dispose();
                    _hashStream = null;
                }
 
                if (_hashAlgorithm != null)
                {
                    ((IDisposable)_hashAlgorithm).Dispose();
                    _hashAlgorithm = null;
                }
 
                if (_signatureFragment != null)
                {
                    _signatureFragment.Dispose();
                    _signatureFragment = null;
                }
 
                if (_endFragment != null)
                {
                    _endFragment.Dispose();
                    _endFragment = null;
                }
 
                if (_writerStream != null)
                {
                    _writerStream.Dispose();
                    _writerStream = null;
                }
 
                if (_preCanonicalTracingStream != null)
                {
                    _preCanonicalTracingStream.Dispose();
                    _preCanonicalTracingStream = null;
                }
            }
 
            // Free native resources, if any.
 
            _disposed = true;
        }
 
        #endregion
    }
}