/// <summary> /// Signs the given mail item using the provided signer. The mailItem object will be updated so that it includes the signature. /// </summary> /// <param name="domainSigner">The domain and its signer</param> /// <param name="mailItem">The mail item to sign</param> /// <returns></returns> public void SignMessage(DomainElementSigner domainSigner, MailItem mailItem) { using (Stream stream = mailItem.GetMimeReadStream()) { stream.Seek(0, SeekOrigin.Begin); Logger.LogDebug("Parsing the MimeMessage"); MimeMessage message = MimeMessage.Load(stream, true); Logger.LogDebug("Signing the message"); lock (settingsMutex) { message.Sign(domainSigner.Signer, eligibleHeaders, headerCanonicalization, bodyCanonicalization); } var value = message.Headers[HeaderId.DkimSignature]; Logger.LogDebug("Got signing header: " + value); // we first need to create a memory stream, because if WriteTo is called with mailItem Stream directly, it throws an exception somehow. MemoryStream memoryOutputStream = new MemoryStream(); message.WriteTo(FormatOptions.Default, memoryOutputStream); memoryOutputStream.Seek(0, SeekOrigin.Begin); using (Stream outputStream = mailItem.GetMimeWriteStream()) { memoryOutputStream.WriteTo(outputStream); outputStream.Close(); } stream.Close(); } }
/// <summary> /// Signs the given mail item using the provided signer. The mailItem object will be updated so that it includes the signature. /// </summary> /// <param name="domainSigner">The domain and its signer</param> /// <param name="mailItem">The mail item to sign</param> /// <returns></returns> public void SignMessage(DomainElementSigner domainSigner, MailItem mailItem) { // MailItem.GetMimeWriteStream() internally uses // Microsoft.Exchange.Data.Mime.MimeDocument.GetLoadStream(), which may reformat the // message using different formatting than is originally read from // MailItem.GetMimeReadStream(). To prevent these formatting changes from invalidating // the DKIM signature, we must read then write then re-read the message to ensure that // any formatting changes are made before we sign the message. using (MemoryStream memStream = new MemoryStream()) { using (Stream inputStream = mailItem.GetMimeReadStream()) { inputStream.Seek(0, SeekOrigin.Begin); #if EX_2007_SP3 || EX_2010 || EX_2010_SP1 || EX_2010_SP2 || EX_2010_SP3 byte[] buffer = new byte[16 * 1024]; int size; while ((size = inputStream.Read(buffer, 0, buffer.Length)) > 0) { memStream.Write(buffer, 0, size); } #else inputStream.CopyTo(memStream); #endif } memStream.Seek(0, SeekOrigin.Begin); using (Stream outputStream = mailItem.GetMimeWriteStream()) { memStream.WriteTo(outputStream); } } using (Stream inputStream = mailItem.GetMimeReadStream()) { inputStream.Seek(0, SeekOrigin.Begin); Logger.LogDebug("Parsing the MimeMessage"); MimeMessage message = MimeMessage.Load(inputStream, true); // 'inputStream' cannot be disposed until we are done with 'message' Logger.LogDebug("Signing the message"); lock (settingsMutex) { message.Sign(domainSigner.Signer, eligibleHeaders, headerCanonicalization, bodyCanonicalization); } var value = message.Headers[HeaderId.DkimSignature]; Logger.LogDebug("Got signing header: " + value); // The Stream returned by mailItem.GetMimeWriteStream() will throw an exception if // Stream.Write() is called after Stream.Flush() has been called, but // MimeMessage.WriteTo(FormatOptions, Stream) may call Stream.Flush() before the full // message has been written. To avoid exceptions we must buffer the message in a // MemoryStream. using (MemoryStream memStream = new MemoryStream()) { message.WriteTo(FormatOptions.Default, memStream); memStream.Seek(0, SeekOrigin.Begin); using (Stream outputStream = mailItem.GetMimeWriteStream()) { memStream.WriteTo(outputStream); } } } }