e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
321 lines
13 KiB
C#
321 lines
13 KiB
C#
//------------------------------------------------------------
|
|
// 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
|
|
}
|
|
}
|