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