1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4 
5 
6 namespace System.IdentityModel
7 {
8     using System;
9     using System.Diagnostics;
10     using System.IdentityModel.Selectors;
11     using System.IdentityModel.Tokens;
12     using System.IO;
13     using System.Security.Cryptography;
14     using System.Text;
15     using System.Xml;
16     using System.Runtime;
17 
18     /// <summary>
19     /// Wraps a writer and generates a signature automatically when the envelope
20     /// is written completely. By default the generated signature is inserted as
21     /// the last element in the envelope. This can be modified by explicitily
22     /// calling WriteSignature to indicate the location inside the envelope where
23     /// the signature should be inserted.
24     /// </summary>
25     public sealed class EnvelopedSignatureWriter : DelegatingXmlDictionaryWriter
26     {
27         DictionaryManager _dictionaryManager;
28         XmlWriter _innerWriter;
29         SigningCredentials _signingCreds;
30         string _referenceId;
31         SecurityTokenSerializer _tokenSerializer;
32         HashStream _hashStream;
33         HashAlgorithm _hashAlgorithm;
34         int _elementCount;
35         MemoryStream _signatureFragment;
36         MemoryStream _endFragment;
37         bool _hasSignatureBeenMarkedForInsert;
38         MemoryStream _writerStream;
39         MemoryStream _preCanonicalTracingStream;
40         bool _disposed;
41 
42         /// <summary>
43         /// Initializes an instance of <see cref="EnvelopedSignatureWriter"/>. The returned writer can be directly used
44         /// to write the envelope. The signature will be automatically generated when
45         /// the envelope is completed.
46         /// </summary>
47         /// <param name="innerWriter">Writer to wrap/</param>
48         /// <param name="signingCredentials">SigningCredentials to be used to generate the signature.</param>
49         /// <param name="referenceId">The reference Id of the envelope.</param>
50         /// <param name="securityTokenSerializer">SecurityTokenSerializer to serialize the signature KeyInfo.</param>
51         /// <exception cref="ArgumentNullException">One of he input parameter is null.</exception>
52         /// <exception cref="ArgumentException">The string 'referenceId' is either null or empty.</exception>
EnvelopedSignatureWriter(XmlWriter innerWriter, SigningCredentials signingCredentials, string referenceId, SecurityTokenSerializer securityTokenSerializer)53         public EnvelopedSignatureWriter(XmlWriter innerWriter, SigningCredentials signingCredentials, string referenceId, SecurityTokenSerializer securityTokenSerializer)
54         {
55             if (innerWriter == null)
56             {
57                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("innerWriter");
58             }
59 
60             if (signingCredentials == null)
61             {
62                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("signingCredentials");
63             }
64 
65             if (string.IsNullOrEmpty(referenceId))
66             {
67                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.ID0006), "referenceId"));
68             }
69 
70             if (securityTokenSerializer == null)
71             {
72                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("securityTokenSerializer");
73             }
74 
75             // Remember the user's writer here. We need to finally write out the signed XML
76             // into this writer.
77             _dictionaryManager = new DictionaryManager();
78             _innerWriter = innerWriter;
79             _signingCreds = signingCredentials;
80             _referenceId = referenceId;
81             _tokenSerializer = securityTokenSerializer;
82 
83             _signatureFragment = new MemoryStream();
84             _endFragment = new MemoryStream();
85             _writerStream = new MemoryStream();
86 
87             XmlDictionaryWriter effectiveWriter = XmlDictionaryWriter.CreateTextWriter(_writerStream, Encoding.UTF8, false);
88 
89             // Initialize the base writer to the newly created writer. The user should write the XML
90             // to this.
91             base.InitializeInnerWriter(effectiveWriter);
92             _hashAlgorithm = CryptoHelper.CreateHashAlgorithm(_signingCreds.DigestAlgorithm);
93             _hashStream = new HashStream(_hashAlgorithm);
94             base.InnerWriter.StartCanonicalization(_hashStream, false, null);
95 
96             //
97             // Add tracing for the un-canonicalized bytes
98             //
99             if (DiagnosticUtility.ShouldTraceVerbose)
100             {
101                 _preCanonicalTracingStream = new MemoryStream();
102                 base.InitializeTracingWriter(new XmlTextWriter(_preCanonicalTracingStream, Encoding.UTF8));
103             }
104         }
105 
ComputeSignature()106         private void ComputeSignature()
107         {
108             PreDigestedSignedInfo signedInfo = new PreDigestedSignedInfo(_dictionaryManager);
109             signedInfo.AddEnvelopedSignatureTransform = true;
110             signedInfo.CanonicalizationMethod = XD.ExclusiveC14NDictionary.Namespace.Value;
111             signedInfo.SignatureMethod = _signingCreds.SignatureAlgorithm;
112             signedInfo.DigestMethod = _signingCreds.DigestAlgorithm;
113             signedInfo.AddReference(_referenceId, _hashStream.FlushHashAndGetValue(_preCanonicalTracingStream));
114 
115             SignedXml signedXml = new SignedXml(signedInfo, _dictionaryManager, _tokenSerializer);
116             signedXml.ComputeSignature(_signingCreds.SigningKey);
117             signedXml.Signature.KeyIdentifier = _signingCreds.SigningKeyIdentifier;
118             signedXml.WriteTo(base.InnerWriter);
119             ((IDisposable)_hashStream).Dispose();
120             _hashStream = null;
121         }
122 
OnEndRootElement()123         private void OnEndRootElement()
124         {
125             if (!_hasSignatureBeenMarkedForInsert)
126             {
127                 // Default case. Signature is added as the last child element.
128                 // We still have to compute the signature. Write end element as a different fragment.
129 
130                 ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).StartFragment(_endFragment, false);
131                 base.WriteEndElement();
132                 ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).EndFragment();
133             }
134             else if (_hasSignatureBeenMarkedForInsert)
135             {
136                 // Signature should be added to the middle between the start and element
137                 // elements. Finish the end fragment and compute the signature and
138                 // write the signature as a seperate fragment.
139                 base.WriteEndElement();
140                 ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).EndFragment();
141             }
142 
143             // Stop Canonicalization.
144             base.EndCanonicalization();
145 
146             // Compute signature and write it into a seperate fragment.
147             ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).StartFragment(_signatureFragment, false);
148             ComputeSignature();
149             ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).EndFragment();
150 
151             // Put all fragments together. The fragment before the signature is already written into the writer.
152             ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).WriteFragment(_signatureFragment.GetBuffer(), 0, (int)_signatureFragment.Length);
153             ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).WriteFragment(_endFragment.GetBuffer(), 0, (int)_endFragment.Length);
154 
155             // _startFragment.Close();
156             _signatureFragment.Close();
157             _endFragment.Close();
158 
159             _writerStream.Position = 0;
160             _hasSignatureBeenMarkedForInsert = false;
161 
162             // Write the signed stream to the writer provided by the user.
163             // We are creating a Text Reader over a stream that we just wrote out. Hence, it is safe to
164             // create a XmlTextReader and not a XmlDictionaryReader.
165             // Note: reader will close _writerStream on Dispose.
166             XmlReader reader = XmlDictionaryReader.CreateTextReader(_writerStream, XmlDictionaryReaderQuotas.Max);
167             reader.MoveToContent();
168             _innerWriter.WriteNode(reader, false);
169             _innerWriter.Flush();
170             reader.Close();
171             base.Close();
172         }
173 
174         /// <summary>
175         /// Sets the position of the signature within the envelope. Call this
176         /// method while writing the envelope to indicate at which point the
177         /// signature should be inserted.
178         /// </summary>
WriteSignature()179         public void WriteSignature()
180         {
181             base.Flush();
182             if (_writerStream == null || _writerStream.Length == 0)
183             {
184                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID6029)));
185             }
186 
187             if (_signatureFragment.Length != 0)
188             {
189                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID6030)));
190             }
191 
192             Fx.Assert(_endFragment != null && _endFragment.Length == 0, SR.GetString(SR.ID8026));
193 
194             // Capture the remaing as a seperate fragment.
195             ((IFragmentCapableXmlDictionaryWriter)base.InnerWriter).StartFragment(_endFragment, false);
196 
197             _hasSignatureBeenMarkedForInsert = true;
198         }
199 
200         /// <summary>
201         /// Overrides the base class implementation. When the last element of the envelope is written
202         /// the signature is automatically computed over the envelope and the signature is inserted at
203         /// the appropriate position, if WriteSignature was explicitly called or is inserted at the
204         /// end of the envelope.
205         /// </summary>
WriteEndElement()206         public override void WriteEndElement()
207         {
208             _elementCount--;
209             if (_elementCount == 0)
210             {
211                 base.Flush();
212                 OnEndRootElement();
213             }
214             else
215             {
216                 base.WriteEndElement();
217             }
218         }
219 
220         /// <summary>
221         /// Overrides the base class implementation. When the last element of the envelope is written
222         /// the signature is automatically computed over the envelope and the signature is inserted at
223         /// the appropriate position, if WriteSignature was explicitly called or is inserted at the
224         /// end of the envelope.
225         /// </summary>
WriteFullEndElement()226         public override void WriteFullEndElement()
227         {
228             _elementCount--;
229             if (_elementCount == 0)
230             {
231                 base.Flush();
232                 OnEndRootElement();
233             }
234             else
235             {
236                 base.WriteFullEndElement();
237             }
238         }
239 
240         /// <summary>
241         /// Overrides the base class. Writes the specified start tag and associates
242         /// it with the given namespace.
243         /// </summary>
244         /// <param name="prefix">The namespace prefix of the element.</param>
245         /// <param name="localName">The local name of the element.</param>
246         /// <param name="ns">The namespace URI to associate with the element.</param>
WriteStartElement(string prefix, string localName, string ns)247         public override void WriteStartElement(string prefix, string localName, string ns)
248         {
249             _elementCount++;
250             base.WriteStartElement(prefix, localName, ns);
251         }
252 
253         #region IDisposable Members
254 
255         /// <summary>
256         /// Releases the unmanaged resources used by the System.IdentityModel.Protocols.XmlSignature.EnvelopedSignatureWriter and optionally
257         /// releases the managed resources.
258         /// </summary>
259         /// <param name="disposing">
260         /// True to release both managed and unmanaged resources; false to release only unmanaged resources.
261         /// </param>
Dispose(bool disposing)262         protected override void Dispose(bool disposing)
263         {
264             base.Dispose(disposing);
265 
266             if (_disposed)
267             {
268                 return;
269             }
270 
271             if (disposing)
272             {
273                 //
274                 // Free all of our managed resources
275                 //
276                 if (_hashStream != null)
277                 {
278                     _hashStream.Dispose();
279                     _hashStream = null;
280                 }
281 
282                 if (_hashAlgorithm != null)
283                 {
284                     ((IDisposable)_hashAlgorithm).Dispose();
285                     _hashAlgorithm = null;
286                 }
287 
288                 if (_signatureFragment != null)
289                 {
290                     _signatureFragment.Dispose();
291                     _signatureFragment = null;
292                 }
293 
294                 if (_endFragment != null)
295                 {
296                     _endFragment.Dispose();
297                     _endFragment = null;
298                 }
299 
300                 if (_writerStream != null)
301                 {
302                     _writerStream.Dispose();
303                     _writerStream = null;
304                 }
305 
306                 if (_preCanonicalTracingStream != null)
307                 {
308                     _preCanonicalTracingStream.Dispose();
309                     _preCanonicalTracingStream = null;
310                 }
311             }
312 
313             // Free native resources, if any.
314 
315             _disposed = true;
316         }
317 
318         #endregion
319     }
320 }
321