I have located my smime.p7m from my email message, I read it as stream and try to decrypt it using MimeKit, but it failed with Operation is not valid due to the current state of the object.
using (MemoryStream ms = new MemoryStream(data)) {
CryptographyContext.Register(typeof(WindowsSecureMimeContext));
ApplicationPkcs7Mime p7m = new ApplicationPkcs7Mime(SecureMimeType.EnvelopedData, ms);
var ctx = new WindowsSecureMimeContext(StoreLocation.CurrentUser);
p7m.Verify(ctx, out MimeEntity output);
}
Following the example on https://github.com/jstedfast/MimeKit doesn't help either. Anyone familiar with MimeKit could chime in?
EDIT:
After decrypting the p7m, am I supposed to use the MimeParser to parse the content? I got the following from the decryption:
Content-Type: application/x-pkcs7-mime; name=smime.p7m; smime-type=signed-data
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=smime.p7m
MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAaCAJIAEWUNvbnRl
bnQtVHlwZTogdGV4dC9wbGFpbjsNCgljaGFyc2V0PSJ1cy1hc2NpaSINCkNvbnRlbnQtVHJhbnNm
ZXItRW5jb2Rpbmc6IDdiaXQNCg0KdGVzdA0KAAAAAAAAoIImTTCCBaIwggOKoAMCAQICBguC3JQz
...more...
But when parsing with MimeParser,
System.FormatException: Failed to parse message headers.
at MimeKit.MimeParser.ParseMessage(Byte* inbuf, CancellationToken cancellationToken)
at MimeKit.MimeParser.ParseMessage(CancellationToken cancellationToken)
UPDATE:
Ah, so it turns, calling Decrypt only gives me the SignedData, I need to then call Verify to pull the original data... this is kind of misleading, I thought Verify would simply verify it... which is why I didn't bother calling it, since I don't really need to verify it... Perhaps it should be call Decode instead? That's what I was trying to do originally, ((MimePart) signedData).Content.DecodeTo(...).
So in the end, I had to do something like this to extract the data.
CryptographyContext.Register(typeof(WindowsSecureMimeContext));
ApplicationPkcs7Mime p7m = new ApplicationPkcs7Mime(SecureMimeType.EnvelopedData, ms);
var ctx = new WindowsSecureMimeContext(StoreLocation.CurrentUser);
if (p7m != null && p7m.SecureMimeType == SecureMimeType.EnvelopedData)
{
// the top-level MIME part of the message is encrypted using S/MIME
p7m = p7m.Decrypt() as ApplicationPkcs7Mime;
}
if (p7m != null && p7m.SecureMimeType == SecureMimeType.SignedData)
{
p7m.Verify(out MimeEntity original); // THE REAL DECRYPTED DATA
using (MemoryStream dump = new MemoryStream())
{
original.WriteTo(dump);
decrypted = dump.GetBuffer();
}
}
You are getting an InvalidOperationException because you are calling Verify() on a EncryptedData.
You need to call Decrypt().
Verify() is for SignedData.
Related
I'm attempting to make use of the Bouncy Castle apis to verify the signature of a clear signed message. (I've got signing, encryption, and decryption working, but some of the things I need to verify won't have been encrypted so I need to be able to do these separately).
I want to verify the signature, and if it is verified then return just the message text (without signature).
My function at the moment is the following;
public string GetVerifiedMessage(string signedCleartext, PgpPublicKey publicKey)
{
var inputStream = PgpUtilities.GetDecoderStream(ReadAsStream(signedCleartext));
var outStr = new MemoryStream();
inputStream.CopyTo(outStr);
outStr.Close();
var objectFactory = new PgpObjectFactory(inputStream);
var signatureList = (PgpSignatureList) objectFactory.NextPgpObject();
var signature = signatureList[0];
var literalData = (PgpLiteralData) objectFactory.NextPgpObject();
var data = literalData.GetInputStream();
signature.InitVerify(publicKey);
var memoryStream = new MemoryStream();
int ch;
while ((ch = data.ReadByte()) >= 0)
{
signature.Update((byte) ch);
memoryStream.WriteByte((byte) ch);
}
if (!signature.Verify())
{
throw new Exception("Signature was not verified");
}
return Encoding.UTF8.GetString(memoryStream.ToArray());
}
This is based off the examples in the bouncy castle codebase and elsewhere, although I have had to vary them to do this in memory rather than writing to files.
When running this, I get an error because the objectFactory does not contain any pgp objects. I have verified that third party tools can verify the signature on the example message I'm using.
I've tried a few variations - not copying to outStr gives an "Unknown object in stream 45" exception on trying to get the PgpObject, which is annoying as in it's current form it is useless.
Can anyone point out where I'm going wrong?
I have been looking through many answers on this I just can't quite seem to understand how to do it though, even if its glaringly obvious.
I want to write values from a table in my database to a text file and attach the text file to an email without storing it locally. I am using Sendgrid API to manage sending the email and I was attempting to use MemoryStream to store the data.
Below is what I have attempted
private MemoryStream WriteToTextFile(IEnumerable<Location> locations)
{
MemoryStream memoryStream = new MemoryStream();
using (StreamWriter streamWriter = new StreamWriter(memoryStream))
{
foreach (var location in locations)
{
streamWriter.WriteLine($"{location.Time},{location.Location},{ location.LocationAccuracy},{ location.IsAlertRaised}");
}
streamWriter.Flush();
}
return memoryStream;
}
Then my attempt to attach it to the email
memoryStream.Seek(0, SeekOrigin.Begin);
var message = MailHelper.CreateSingleEmail(from, to, subject, plainTextContent, htmlContent);
message.AddAttachment(memoryStream, fileName);
var response = await _client.SendEmailAsync(message);
memoryStream.Dispose();
The error is straightforward "cannot convert from 'System.IO.MemoryStream' to 'string'
The solution I was attempting was based on a question asked here.
AddAttachment from MemoryStream
There are two problems here.
The using block in the first sample will call streamWriter.Dispose() before returning the stream. The Dispose() operation will close your MemoryStream, leaving it unusable.
The example code from your link seems to be using a different, possibly older, version of the SendGrid API. Looking at the current API, the only overload from the AddAttachment() family that accepts a stream looks like this:
public async Task AddAttachmentAsync(string filename, Stream contentStream, string type = null, string disposition = null, string content_id = null, CancellationToken cancellationToken = default(CancellationToken))
All of the other options required a base64content string instead. You can also just read the code for the method in the API link above for a good example of how to convert that stream to base64.
The SendGrid API docs specify you can add attachments from a Stream. The example it gives uses a FileStream object.
I have some blobs in Azure Storage which I'd like to email as attachments. To achieve this I'm trying to use a MemoryStream:
var getBlob = blobContainer.GetBlobReferenceFromServer(fileUploadLink.Name);
if(getBlob != null)
{
// Get file as a stream
MemoryStream memoryStream = new MemoryStream();
getBlob.DownloadToStream(memoryStream);
emailMessage.AddAttachment(memoryStream, fileUploadLink.Name);
}
emailTransport.Deliver(emailMessage);
It sends fine but when the email arrives, the attachment appears to be there but it's actually empty. Looking at the email source, there is no content for the attachment.
Is using a MemoryStream a known limitation when using the SendGrid C# API to send attachments? Or should I be approaching this in some other way?
You probably just need to reset the stream position back to 0 after you call DownloadToStream:
var getBlob = blobContainer.GetBlobReferenceFromServer(fileUploadLink.Name);
if (getBlob != null)
{
var memoryStream = new MemoryStream();
getBlob.DownloadToStream(memoryStream);
memoryStream.Seek(0,SeekOrigin.Begin); // Reset stream back to beginning
emailMessage.AddAttachment(memoryStream, fileUploadLink.Name);
}
emailTransport.Deliver(emailMessage);
You might want to check who cleans up the stream as well and if they don't you should dispose of it after you've called Deliver().
According to their API, they have implemented void AddAttachment(Stream stream, String name).
You are probably using a MemoryStream which you have written to before. I suggest resetting the position inside the stream to the beginning, like:
memoryStream.Seek(0, SeekOrigin.Begin);
I ended up with the following which fixed the issue for me:
fileByteArray = new byte[getBlob.Properties.Length];
getBlob.DownloadToByteArray(fileByteArray, 0);
attachmentFileStream = new MemoryStream(fileByteArray);
emailMessage.AddAttachment(attachmentFileStream, fileUploadLink.Name);
The thread is a bit old, but I use a varient with NReco PDF converter:
private async Task SendGridasyncBid(string from, string to, string displayName, string subject, **byte[] PDFBody**, string TxtBody, string HtmlBody)
{
...
var myStream = new System.IO.MemoryStream(**PDFBody**);
myStream.Seek(0, SeekOrigin.Begin);
myMessage.AddAttachment(myStream, "NewBid.pdf");
...
}
convert the html to pdf and return it instead of writing it for download...
private byte[] getHTML(newBidViewModel model)
{
string strHtml = ...;
HtmlToPdfConverter pdfConverter = new HtmlToPdfConverter();
pdfConverter.CustomWkHtmlArgs = "--page-size Letter";
var pdfBytes = pdfConverter.GeneratePdf(strHtml);
return **pdfBytes**;
}
I am not sure how efficient this is, but it is working for me and I hope it helps someone else get their attachments figured out.
I'm trying to attach an XElement to an SMTP message i'm sending.
My code looks like this:
XElement xmlMsg = new XElement("Test",new XElement("TestSon", "DummyValue"),new XElement("TestSon2","DummyValue"));
using (MemoryStream memoryStream = new MemoryStream())
{
byte[] contentAsBytes = Encoding.Default.GetBytes(xmlMsg.ToString());
memoryStream.Write(contentAsBytes, 0, contentAsBytes.Length);
// Set the position to the beginning of the stream.
memoryStream.Seek(0, SeekOrigin.Begin);
// Create attachment
ContentType contentType = new ContentType();
contentType.MediaType = MediaTypeNames.Text.Plain;
contentType.Name = "Conversation.xml";
Attachment attachment = new Attachment(memoryStream, contentType);
mail.Attachments.Add(attachment);
Server.Send(mail);
}
However, my email is received with the XML file clipped at the end, without the last 2 chars...
Am i missing something here?
Thanks
What encoding is Encoding.Default on your system?
If it is UTF-16 I expect the two bytes are a BOM which is (for some reason) not being included in the byte count.
Suggestions:
Put xmlMsg.ToString() into a local variable, and check it in the debugger.
Have a look at the bytes in memoryStream
Look at the content of the email message in the raw, possibly copying the unencoded attachment so you can manually decode as a binary file.
Ie. check each step with as little automatic re-interpretation (eg. by an XML viewer) as possible.
I am trying to send large objects (>30MB) to a MSMQ queue. Due to the large amount of data we are are tring to send the idea was to GZip the objects prior to sending them, then unzipping them on the receiving end.
However, writing the compressed stream to the message.BodyStream property seems to work, but not reading it out from there.
I don't know what's wrong.
Message l_QueueMessage = new Message();
l_QueueMessage.Priority = priority;
using (MessageQueue l_Queue = CreateQueue())
{
GZipStream stream = new GZipStream(l_QueueMessage.BodyStream, CompressionMode.Compress);
Formatter.Serialize(stream, message);
l_Queue.Send(l_QueueMessage);
}
The Formatter is a global property of type BinaryFormatter. This is used to serialize/deserialize to the type of object we want to send/receive, e.g. "ProductItem".
The receving end looks like this:
GZipStream stream = new GZipStream(l_Message.BodyStream, CompressionMode.Decompress);
object decompressedObject = Formatter.Deserialize(stream);
ProductItem l_Item = decompressedObject as ProductItem;
m_ProductReceived(sender, new MessageReceivedEventArgs<ProductItem>(l_Item));
l_ProductQueue.BeginReceive();
I get an EndOfStreamException "{"Unable to read beyond the end of the stream."} trying to deserialize
at System.IO.BinaryReader.ReadByte()
Using the messageBodyStream property I actually circumvent the message.Formatter, which I don't initialize to anything, becasue I'm using my own ser/deser mechanism with the GZipStream. However, I am not sure if that's the correct way of doing this.
What am I missing?
Thanks!
In your original code, the problem is that you need to close the GZipStream in order for a GZip footer to be written correctly, and only then you can send it. If you dont, you end up sending bytes that can not be deserialized. That's also why your new code where sending is done later works.
OK, I made this work. The key was to convert the decompressed stream on the receiver to a byte[] array. Then the deserialization started working.
The sender code (notice the stream is closed before sending the message):
using (MessageQueue l_Queue = CreateQueue())
{
using (GZipStream stream = new GZipStream(l_QueueMessage.BodyStream, CompressionMode.Compress, true))
{
Formatter.Serialize(stream, message);
}
l_Queue.Send(l_QueueMessage);
}
The receiving end (notice how I convert the stream to a byte[] then deserialize):
using (GZipStream stream = new GZipStream(l_QueueMessage.BodyStream, CompressionMode.Decompress))
{
byte[] bytes = ReadFully(stream);
using (MemoryStream ms = new MemoryStream(bytes))
{
decompressedObject = Formatter.Deserialize(ms);
}
}
Still, don't know why this works using the ReadFully() function and not the Stream.CopyTo().
Does anyone?
Btw, ReadFully() is a function that creates a byte[] out of a Stream. I have to credit Jon Skeet for this at http://www.yoda.arachsys.com/csharp/readbinary.html. Thanks!
Try to separate compressing and sending:
byte[] binaryBuffer = null;
using (MemoryStream compressedBody = new MemoryStream())
{
using(GZipStream stream = new GZipStream(compressedBody, CompressionMode.Compress))
{
Formatter.Serialize(compressedBody, message);
binaryBuffer = compressedBody.GetBuffer();
}
}
using (MessageQueue l_Queue = CreateQueue())
{
l_QueueMessage.BodyStream.Write(binaryBuffer, 0, binaryBuffer.Length);
l_QueueMessage.BodyStream.Seek(0, SeekOrigin.Begin);
l_Queue.Send(l_QueueMessage);
}