S/Mime with attachment sign using MailSystem.Net - c#

I'm new to s/mime and need to digitally sign email with xml attachment, but unfortunately this email has a wrong hash value (according to response of external system). I digged into the code of the library and found that it creates a sign for base64-encoded body part, is it correct or the signature should be computed for xml attachment content?
Also here is some more issues:
Lots of headers/parameters are owerritten by library: for ex. ContentType parameters, some headers (like X-Mailer) and many others
It creates an empty boundary for Content-Type: text/plain, though I haven't any text except attachment
Here is my code:
public static void Sign(X509Certificate2 clientCert, string from, string to, string subject, string attachementPath)
{
Message message = new Message();
message.From = new Address(from);
message.To.Add(to);
message.ContentType.MimeType = "multipart/signed";
message.ContentType.Parameters.Add("protocol", "\"application/pkcs7-signature\"");
message.ContentTransferEncoding = ContentTransferEncoding.SevenBits;
message.AddHeaderField("MIME-Version", "1.0");
message.Subject = subject;
var mimePart = new MimePart(attachementPath, false);
mimePart.ContentTransferEncoding = ContentTransferEncoding.Base64;
mimePart.Charset = "windows-1251";
mimePart.ContentType.MimeType = "text/xml";
message.Attachments.Add(mimePart);
message.BuildMimePartTree();
CmsSigner signer = new CmsSigner(clientCert);
signer.IncludeOption = X509IncludeOption.EndCertOnly;
message.SmimeAttachSignatureBy(signer);
}

Related

Send images using sendgrid C#

I am using sendgrid to send images in mails, but I am not sure what's wrong with my code.
Here is my code:
string imgPath= Server.MapPath(#"~/Images/logo-img.png");
StringBuilder sb = new StringBuilder();
sb.Append("Hi There! I am using sendgrid to send images");
sb.Append("<a href='someurl'><img src='cid:myImage.png> Login in</a>");
LinkedResource lr = new LinkedResource(imgPath, "image/png");
lr.ContentId = "myImage";
AlternateView AV = AlternateView.CreateAlternateViewFromString(sb.ToString(), null, "image/png");
AV.LinkedResources.Add(lr);
var from = new EmailAddress("abc#bac.com");
var subject = "Hi There!";
var to = new EmailAddress("xyz#xyz.com");
var plainTextContent = "Hello";
var htmlContent = sb.ToString();
var msg = MailHelper.CreateSingleEmail(from, to, subject, plainTextContent, htmlContent);
msg.SetFrom(from, "abc");
var sendingResult = SendMail(msg);
You put quote in wrong place:
sb.Append("<a href='someurl'><img src='cid:myImage.png'> Login in</a>");
I don't know what the LinkedResource or AlternateView classes do, but they don't appear to be part of the Twilio SendGrid C# library.
There is an example in the library docs for sending attachments, but it doesn't show how to address the content within the email. But it's close. The key is that you need to load the file you want to attach and transform it to base64, then use the AddAttachment method to add it as an inline attachment to your mail, passing the content ID (cid) that you can refer to in the body of the message. Something like:
string imgPath= Server.MapPath(#"~/Images/logo-img.png");
StringBuilder sb = new StringBuilder();
sb.Append("Hi There! I am using sendgrid to send images");
sb.Append("<a href='someurl'><img src='cid:myImage'> Login in</a>");
var bytes = File.ReadAllBytes(imgPath);
var file = Convert.ToBase64String(bytes);
var from = new EmailAddress("abc#bac.com");
var subject = "Hi There!";
var to = new EmailAddress("xyz#xyz.com");
var plainTextContent = "Hello";
var htmlContent = sb.ToString();
var msg = MailHelper.CreateSingleEmail(from, to, subject, plainTextContent, htmlContent);
msg.SetFrom(from, "abc");
msg.AddAttachment("logo-img.png", file, "image/png", "inline", "myImage");
var sendingResult = SendMail(msg);
In this updated code the important parts are loading the file as base64:
var bytes = File.ReadAllBytes(imgPath);
var file = Convert.ToBase64String(bytes);
and attaching the file to the message:
msg.AddAttachment("logo-img.png", file, "image/png", "inline", "myImage");
The AddAttachment method takes 5 arguements: the filename, the base64 content of the file, the mime type, the content disposition (for referring to images in the HTML you should use "inline"), and the cid. Note also that the cid is the entire string you should use to refer to the image, so if the image cid is "myImage" then you should refer to it as cid:myImage, like:
sb.Append("<a href='someurl'><img src='cid:myImage'> Login in</a>");

Sending MHT file using C# Mail Message

Has anyone got any sample code to send MHT using SmtpClient and MailMessage in C#?
I have found tantalising references to using Alternate Views but I cannot figure out how to make this work with my SSRS generated MHT.
If anyone has any working code they are willing to share I would be greatly appreciative.
So it appears that .NET MailMessage does not support this out of the box. I have found a couple of approaches that can be used with varying degrees of success.
MHTML -> HTML Decoder
David Benko wrote https://github.com/DavidBenko/MHTML-to-HTML-Decoding-in-C-Sharp/blob/master/MHTMLParser.cs and while it works, I was unable to make the images appear when I viewed the resulting email. The HTML looked right, it just did not work in Outlook 2010.
Convert MimeKit MimeMessage to .NET Mail Message
As Jeffrey points out this is a start https://github.com/jstedfast/MimeKit/issues/140. I had problems again with the images not being displayed in the resulting message.
Use MimeKit & MailKit
I ended up using https://github.com/jstedfast/MimeKit to do the job. This can be achieved as follows:
MimeMessage messageMimeKit = MimeMessage.Load(Request.MapPath("~/Blah/378921.mht"));
messageMimeKit.From.Add(new MailboxAddress("Dev", "developer#gmail.com"));
messageMimeKit.To.Add(new MailboxAddress("Homer", "homer#gmail.com"));
messageMimeKit.Subject = "Another subject line";
using (var client = new MailKit.Net.Smtp.SmtpClient())
{
client.Connect("smtp.gmail.com", 465, true);
client.Authenticate("homer#gmail.com", "*****");
client.Send(messageMimeKit);
client.Disconnect(true);
}
Using System.Net.Http.Formatting
My approach leverages the ReadAsMultipartAsync extension methods on HttpContent found in the System.Net.Http.Formatting library. (part of the Microsoft.AspNet.WebApi.Client nuget package)
The only tricky bit is getting the correct content type out of the file. In order for the ReadAsMultipartAsync method to work, it needs the ContentType header on the stream content set to the correct type ("multipart/related"), including the correct boundary parameter on the content type value:
multipart/related;
boundary="----=_NextPart_01C35DB7.4B204430"
Please note, since my solution was only written for sending content from SSRS reports exported in the MTHML format, my code assumes the multipart content has only one text/html part with the remaining parts being images, but the technique is sound and could be adjusted to handle multipart content in a more generic way.
const string ContentTypePrefix = "Content-Type: ";
AlternateView htmlView = null;
var linkedResources = new List<LinkedResource>();
var content = File.ReadAllText("report.mhtml");
var startIndex = content.IndexOf(ContentTypePrefix, StringComparison.OrdinalIgnoreCase) + ContentTypePrefix.Length;
var endIndex = content.IndexOf("\"", startIndex, StringComparison.Ordinal) + 1;
var contentType = content.Substring(startIndex, endIndex);
using (var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(content)))
{
using (var stream = new StreamContent(memoryStream))
{
stream.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
if (!stream.IsMimeMultipartContent())
{
throw new Exception("Not correct format.");
}
var parts = stream.ReadAsMultipartAsync().ConfigureAwait(false).GetAwaiter().GetResult();
foreach (var part in parts.Contents)
{
part.Headers.ContentType.CharSet = part.Headers.ContentType.CharSet?.Replace("\"", string.Empty); // Needed since the SSRS report defaults the charset to \"utf-8\", instead of utf-8 and the code fails to find the correct encoder
var compressedContent = part.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
if (!part.Headers.ContentType.MediaType.Contains("image"))
{
var encoding = Encoding.GetEncoding(part.Headers.ContentType.CharSet);
var partContent = encoding.GetString(Convert.FromBase64String(compressedContent));
htmlView = AlternateView.CreateAlternateViewFromString(partContent, encoding, part.Headers.ContentType.MediaType);
}
else
{
linkedResources.Add(new LinkedResource(new MemoryStream(Convert.FromBase64String(compressedContent)), part.Headers.ContentType.MediaType)
{
ContentId = part.Headers.ContentDisposition.FileName.Replace("\"", string.Empty)
});
}
}
}
}
foreach (var linkedResource in linkedResources)
{
htmlView?.LinkedResources.Add(linkedResource);
}
using (var message = new MailMessage("from#mail.com", "to#mail.com", "Subject", string.Empty))
{
message.BodyEncoding = Encoding.UTF8;
message.AlternateViews.Add(htmlView);
using (var client = new SmtpClient("smtpserver.com", 25))
{
client.Send(message);
}
}

How do I include XML in an email body using mailmessage.body

I have an application that performs some tests. As a result of these tests an XML object is produced (from an XmlDiff compare).
Is there any way to embed the XML into an Email body in a formatted state?
I am using XmlWriter to a StringBuilder object then converting the string into and "html safe" format using System.Security.SecurityElement.Escape(). In the MailMessage, I set IsBodyHtml = true.
To extract the XML I want to embed:
public static string GetDiff(string approvedfile, string failingfile)
{
StringBuilder sb = new StringBuilder();
XmlWriter xmlwriter = XmlWriter.Create(sb);
XmlDiff xdiff =
new XmlDiff(XmlDiffOptions.IgnoreChildOrder | XmlDiffOptions.IgnoreNamespaces |
XmlDiffOptions.IgnorePrefixes);
bool response = xdiff.Compare(approvedfile, failingfile, false, xmlwriter);
xmlwriter.Flush();
xmlwriter.Close();
return System.Security.SecurityElement.Escape(sb.ToString());
}
To set the body content I am using:
string sb = GetDiff(approvedMapfile, fileout);
string message = String.Format("Comparison of Map item1 and Map item2 for {0} failed.</br>Review the differences here:</br>{1}</br> and Compare the files</br>{2}</br>and</br>{3}</br>to find the differences.", loc, sb, emailApprovedMap, emailFailedMap);
Util.SendStatusEmail(message);
in SendStatusEmail the mail information is being set thusly:
public static string SendStatusEmail(string status)
{
string hostServer = "smtphost.this.server.com";
MailMessage mail = new MailMessage();
SmtpClient client = new SmtpClient();
client.Port = 25;
client.Host = hostServer;
client.Credentials = CredentialCache.DefaultNetworkCredentials;
MailAddress from = new MailAddress("me#here.com", "Me There");
mail.From = from;
mail.To.Add("You#there.com");
mail.IsBodyHtml = true;
mail.Body = status;
mail.Subject = "Map Checker status report";
try
{
client.Send(mail);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return string.Empty;
}
When the email is received using the above methodology, the embedded XML is included, but it is not formatted, it appears as one long string of XML.
Is there a way to programmatically embed XML in an email body and have it appear as formatted?
Thanks for your help!
I found solution, in the last of the body message create one div and set style display none, not insert close div only open tag div with style display none.
mail.IsBodyHtml = false;
does the job, unless you really need html formatted body.
Maybe is too old but i have solution to just encode the xml text.
refer how to do on C#: How to encode the ampersand if it is not already encoded?
Basically having lib to do this:
HttpUtility.HtmlEncode(string)
i'm doing with java something like this:
StringEscapeUtils.escapeHtml4("<xml><row>1</row></xml>");

Get file to send as attachment from byte array

I have an ASP.NET MVC application that has to send an email to a list of recipients with an attachment detailing a specific "Project". I get the details for this from a report by making use of SQL Server Reporting Services (SSRS).
I've never really used SSRS before, but I received some code from a colleague where he used it. What he does with the report is he downloads it in the browser to the client's machine. So, if I don't do that I sit with a byte array containing the report data.
Is there a way I can send this as an attachment without first physically writing the file to the filesystem of the server? The report will either be in excel format or a pdf.
Edit: I am using SmtpClient to send the email's.
Get the file data in a byte[]
byte[] binaryFile = // get your file data from the SSRS ...
string filename = "SSRS.pdf";
Prepare a list or array of the destination addresses:
string[] addresses = // get addresses somehow (db/hardcoded/config/...)
sending smtp message through SmtpClient:
MailMessage mailMessage= new MailMessage();
mailMessage.From = new MailAddress("sender email address goes here");
// Loop all your clients addresses
foreach (string address in addresses)
{
mailMessage.To.Add(address);
}
mailMessage.Subject = "your message subject goes here";
mailMessage.Body = "your message body goes here";
MemoryStream memoryStream = new MemoryStream(binaryFile);
mailMessage.Attachments.Add( new Attachment( memoryStream, filename , MediaTypeNames.Application.Pdf ));
SmtpClient smtpClient = new SmtpClient();
smtpClient.Send(mailMessage);
to do this you would need to leverage off the SSRS ReportManager API as follows.
First read in the report from the Web Service with SSRS
Read the file into memory rather than saving to the server or client
Send the MemoryStream object straight to the email server.
Reporting services: Get the PDF of a generated report
How to send an email with attachments using SmtpClient.SendAsync?
string strReportUser = "RSUserName";
string strReportUserPW = "MySecretPassword";
string strReportUserDomain = "DomainName";
string sTargetURL = "http://SqlServer/ReportServer?" +
"/MyReportFolder/Report1&rs:Command=Render&rs:format=PDF&ReportParam=" +
ParamValue;
HttpWebRequest req =
(HttpWebRequest)WebRequest.Create( sTargetURL );
req.PreAuthenticate = true;
req.Credentials = new System.Net.NetworkCredential(
strReportUser,
strReportUserPW,
strReportUserDomain );
HttpWebResponse HttpWResp = (HttpWebResponse)req.GetResponse();
Stream fStream = HttpWResp.GetResponseStream();
HttpWResp.Close();
//Now turn around and send this as the response..
ReadFullyAndSend( fStream );
ReadFullyAnd send method.
NB: the SendAsync call so your not waiting for the server to send the email completely before you are brining the user back out of the land of nod.
public static void ReadFullyAndSend( Stream input )
{
using ( MemoryStream ms = new MemoryStream() )
{
input.CopyTo( ms );
MailMessage message = new MailMessage("from#foo.com", "too#foo.com");
Attachment attachment = new Attachment(ms, "my attachment",, "application/vnd.ms-excel");
message.Attachments.Add(attachment);
message.Body = "This is an async test.";
SmtpClient smtp = new SmtpClient("localhost");
smtp.Credentials = new NetworkCredential("foo", "bar");
smtp.SendAsync(message, null);
}
}

Send encrypted and signed email using C#

I want to send an encrypted and signed mail without using any third-party API.
If I send only the alternate view with the signature, Windows Mail can validate it. If I send only with alternate view with encrypted data, Windows Mail can decipher it. But if I send both, Windows Mail gets 2 attachements. If I sign the encryptedBytes and add those signed bytes to the alternative view it only validates the signature and the message is empty.
Any idea?
MailMessage message = new MailMessage();
message.From = new MailAddress(lblMail.Text);
message.Subject = txtSubject.Text;
string body = "Content-Type: text/plain\r\nContent-Transfer-Encoding: 7Bit\r\n\r\n" + structForm();
byte[] messageData = Encoding.ASCII.GetBytes(body);
ContentInfo content = new ContentInfo(messageData);
EnvelopedCms envelopedCms = new EnvelopedCms(content);
message.To.Add(new MailAddress(provMail));
CmsRecipient recipient = new CmsRecipient(SubjectIdentifierType.SubjectKeyIdentifier, this.certificate);
envelopedCms.Encrypt(recipient);
byte[] encryptedBytes = envelopedCms.Encode();
SignedCms Cms = new SignedCms(new ContentInfo(encryptedBytes));
CmsSigner Signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, new X509Certificate2(#"c:\serv.pfx","123"));
Cms.ComputeSignature(Signer);
byte[] SignedBytes = Cms.Encode();
MemoryStream encryptedStream = new MemoryStream(encryptedBytes);
AlternateView encryptedView = new AlternateView(encryptedStream, "application/pkcs7-mime; smime-type=signed--data;name=smime.p7m");
message.AlternateViews.Add(encryptedView);
MemoryStream signedStream = new MemoryStream(SignedBytes);
AlternateView signedView = new AlternateView(signedStream, "application/pkcs7-mime; smime-type=signed-data;name=sig.p7m");
message.AlternateViews.Add(signedView);
System.Net.NetworkCredential SMTPUserInfo = new System.Net.NetworkCredential("emailaddress#xpto.com", "XXXXXX");
SmtpClient client = new SmtpClient("smtp.xpto.com");
client.UseDefaultCredentials = false;
client.Credentials = SMTPUserInfo;
client.Send(message);
Label2.Text = "Assinado e cifrado!";
You should sign first, then encrypt.
While the original CMS and S/MIME specifications allow you to do the operations in either order, later work pointed out that signing a document that you can't read is a really bad idea. The signature should be over the plain-text.
The resulting MIME message should only have a single part, which should be S/MIME enveloped-data. Your message has two parts, and the encrypted part is mis-labeled with a signed-data content-type. Create and sign the SignedCms object. Encode it, and use the encoded value as the content of an EnvelopedCms object. Encrypt that, and use its encoded value as the content of your MailMessage, with a content type of "application/pkcs7-mime; smime-type=enveloped-data".

Categories

Resources