//------------------------------------------------------------ // 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; /// /// 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. /// 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; /// /// Initializes an instance of . The returned writer can be directly used /// to write the envelope. The signature will be automatically generated when /// the envelope is completed. /// /// Writer to wrap/ /// SigningCredentials to be used to generate the signature. /// The reference Id of the envelope. /// SecurityTokenSerializer to serialize the signature KeyInfo. /// One of he input parameter is null. /// The string 'referenceId' is either null or empty. 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(); } /// /// 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. /// 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; } /// /// 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. /// public override void WriteEndElement() { _elementCount--; if (_elementCount == 0) { base.Flush(); OnEndRootElement(); } else { base.WriteEndElement(); } } /// /// 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. /// public override void WriteFullEndElement() { _elementCount--; if (_elementCount == 0) { base.Flush(); OnEndRootElement(); } else { base.WriteFullEndElement(); } } /// /// Overrides the base class. Writes the specified start tag and associates /// it with the given namespace. /// /// The namespace prefix of the element. /// The local name of the element. /// The namespace URI to associate with the element. public override void WriteStartElement(string prefix, string localName, string ns) { _elementCount++; base.WriteStartElement(prefix, localName, ns); } #region IDisposable Members /// /// Releases the unmanaged resources used by the System.IdentityModel.Protocols.XmlSignature.EnvelopedSignatureWriter and optionally /// releases the managed resources. /// /// /// True to release both managed and unmanaged resources; false to release only unmanaged resources. /// 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 } }